Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
393c420983 | ||
| 456623b403 | |||
|
|
e2a8e89e55 | ||
| 92a5c48e5e | |||
|
|
6766985713 | ||
| c0d05264df | |||
|
|
9505201499 | ||
| 624591c096 | |||
|
|
e31bdce713 | ||
| 5d72beaf8d | |||
| 43f34015c6 | |||
|
|
ac5ce07e61 | ||
| e9fb36cc24 |
2
.idea/workspace.xml
generated
2
.idea/workspace.xml
generated
@@ -817,4 +817,4 @@
|
|||||||
<option value=".github/prompts" />
|
<option value=".github/prompts" />
|
||||||
</promptFileLocations>
|
</promptFileLocations>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
14
AGENTS.md
14
AGENTS.md
@@ -8,6 +8,7 @@ Project overview
|
|||||||
Backend conventions
|
Backend conventions
|
||||||
- Use English for code identifiers/messages; keep “pont-bascule” as domain term.
|
- Use English for code identifiers/messages; keep “pont-bascule” as domain term.
|
||||||
- API Platform operations are defined on Doctrine entities.
|
- API Platform operations are defined on Doctrine entities.
|
||||||
|
- No custom repository classes are used (`src/Repository` removed); use default Doctrine repositories via `EntityManagerInterface`.
|
||||||
- Reception entity is in `src/Entity/Reception.php`, with custom weigh endpoint `/receptions/weigh`.
|
- Reception entity is in `src/Entity/Reception.php`, with custom weigh endpoint `/receptions/weigh`.
|
||||||
- Reception fields: `date_reception`, `license_plate`, `current_step` (default 0), `is_valid` (default false).
|
- Reception fields: `date_reception`, `license_plate`, `current_step` (default 0), `is_valid` (default false).
|
||||||
- Reception also has `identification_number` (auto `N-BR-####`), `merchandise_type`, `merchandise_detail`, `buildings` (M2M), and `pellet_buildings` (via `reception_pellet_building`).
|
- Reception also has `identification_number` (auto `N-BR-####`), `merchandise_type`, `merchandise_detail`, `buildings` (M2M), and `pellet_buildings` (via `reception_pellet_building`).
|
||||||
@@ -17,6 +18,13 @@ Backend conventions
|
|||||||
- Custom exception: `App\Exception\PontBasculeException` with French messages, mapped to 500 in provider.
|
- Custom exception: `App\Exception\PontBasculeException` with French messages, mapped to 500 in provider.
|
||||||
- Parsing of pont-bascule payload is in `src/Service/PontBasculePayloadDecoder.php`.
|
- Parsing of pont-bascule payload is in `src/Service/PontBasculePayloadDecoder.php`.
|
||||||
- `config/reference.php` is auto-generated; keep it.
|
- `config/reference.php` is auto-generated; keep it.
|
||||||
|
- Bovine storage:
|
||||||
|
- `src/Entity/Bovine.php` with fields `nationalNumber` (unique), `receivedWeight`, `arrivalDate`, and `buildingCase` (ManyToOne).
|
||||||
|
- `src/Entity/BuildingCase.php` has `bovines` (OneToMany).
|
||||||
|
- Case PDF report:
|
||||||
|
- Endpoint: `GET /building_cases/{id}/weights-report` (provider: `App\State\BuildingCaseWeightsReportProvider`).
|
||||||
|
- Template: `templates/case_weights_report.html.twig`.
|
||||||
|
- Projection logic is done in backend from `arrivalDate`; daily gain is currently fixed at `1.3 kg/day` for all races.
|
||||||
|
|
||||||
Frontend conventions
|
Frontend conventions
|
||||||
- Nuxt SSR disabled; Tailwind used.
|
- Nuxt SSR disabled; Tailwind used.
|
||||||
@@ -36,6 +44,7 @@ Frontend conventions
|
|||||||
- Service layer lives in `frontend/services/` with typed DTOs in `frontend/services/dto/`.
|
- Service layer lives in `frontend/services/` with typed DTOs in `frontend/services/dto/`.
|
||||||
- Reception service uses `receptions`, `receptions/{id}`, `receptions/weigh` and supports success/error toast keys.
|
- Reception service uses `receptions`, `receptions/{id}`, `receptions/weigh` and supports success/error toast keys.
|
||||||
- Reception receipt endpoint is `receptions/{id}/receipt` (PDF) via `frontend/composables/usePdfPrinter.ts`.
|
- Reception receipt endpoint is `receptions/{id}/receipt` (PDF) via `frontend/composables/usePdfPrinter.ts`.
|
||||||
|
- Infrastructure case page prints the case weight report PDF from `frontend/pages/infrastructure/case.vue` using `usePdfPrinter('/building_cases/{id}/weights-report')`.
|
||||||
|
|
||||||
Environment & routing
|
Environment & routing
|
||||||
- Frontend dev server: `npm run dev` in `frontend/`.
|
- Frontend dev server: `npm run dev` in `frontend/`.
|
||||||
@@ -47,6 +56,11 @@ Environment & routing
|
|||||||
Notes
|
Notes
|
||||||
- Do not add a GET that creates resources; use POST + PATCH.
|
- Do not add a GET that creates resources; use POST + PATCH.
|
||||||
- Keep endpoints in plural (API Platform convention).
|
- Keep endpoints in plural (API Platform convention).
|
||||||
|
- Seed and fixtures conventions:
|
||||||
|
- `app:seed` now seeds infrastructure (`statut`, `building_layout`, `building_case`, `building_case_position`) and bovines.
|
||||||
|
- `app:seed` uses intermediate flushes (after buildings and after infrastructure) so find queries can resolve just-created records.
|
||||||
|
- Bovine seed rows use a legacy case token mapping to building-case code (`B{building}-C{case}`) before fallback to direct id lookup.
|
||||||
|
- Fixtures include `BuildingInfrastructureFixtures` + `BovineFixtures` (via `AppFixtures` dependencies).
|
||||||
- New reference data added:
|
- New reference data added:
|
||||||
- Reception types (`reception_type`, fields: `label`, `code`), selectable on reception form.
|
- Reception types (`reception_type`, fields: `label`, `code`), selectable on reception form.
|
||||||
- Merchandise types (`merchandise_type`, fields: `label`, `code`) and pellet types (`pellet_type`, fields: `label`, `code`).
|
- Merchandise types (`merchandise_type`, fields: `label`, `code`) and pellet types (`pellet_type`, fields: `label`, `code`).
|
||||||
|
|||||||
@@ -51,7 +51,10 @@ Ajouter dans le fichier .env du frontend
|
|||||||
* Creation page admin ajout/modification bovins
|
* Creation page admin ajout/modification bovins
|
||||||
* [#331] Mettre à jour l'entité Shipment et bovin_shipment
|
* [#331] Mettre à jour l'entité Shipment et bovin_shipment
|
||||||
* [#278] Plan du site
|
* [#278] Plan du site
|
||||||
|
* [#334] Correctifs
|
||||||
|
* [#332] Refonte écran réception terminée
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.0.53'
|
app.version: '0.0.59'
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -2,17 +2,17 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NuxtLink :to="link">
|
<NuxtLink :to="link">
|
||||||
<div class="w-[300px] h-[216px] border border-black rounded-lg p-6 flex flex-col justify-between gap-4">
|
<div class="w-[300px] h-[216px] border border-primary-700 rounded-lg p-6 flex flex-col justify-between gap-4">
|
||||||
<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-[#D9D9D9] flex justify-center items-center">
|
||||||
<Icon :name="iconName" style="color: black" size="44" />
|
<Icon :name="iconName" class="!text-primary-700" size="44" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Icon name="mdi:plus" style="color: black" size="44" />
|
<Icon name="mdi:plus" style="color: black" size="44" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="uppercase font-bold">
|
<div class="uppercase font-bold">
|
||||||
<p class="text-3xl text-primary-500">
|
<p class="text-3xl text-primary-700">
|
||||||
<slot name="label">{{ label }}</slot>
|
<slot name="label">{{ label }}</slot>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<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 gap-16">
|
||||||
<h1 class="text-4xl uppercase font-bold text-primary-500">Sélection des races réceptionnées</h1>
|
<h1 class="text-4xl uppercase font-bold text-primary-500">Sélection des races réceptionnées</h1>
|
||||||
<div
|
<div
|
||||||
class="flex flex-row gap-8 items-center">
|
class="flex flex-row gap-8 items-center w-full">
|
||||||
<div
|
<div
|
||||||
v-for="type in bovineType"
|
v-for="type in bovineType"
|
||||||
:key="type.id"
|
:key="type.id"
|
||||||
class="mt-8 flex flex-row mb-2 gap-6">
|
class="mt-8 flex flex-row mb-2 w-full">
|
||||||
<UiNumberInput
|
<UiNumberInput
|
||||||
:id="type.id"
|
:id="type.id"
|
||||||
:label="type.label"
|
:label="type.label"
|
||||||
@@ -17,6 +17,8 @@
|
|||||||
:placeholder="0"
|
:placeholder="0"
|
||||||
:min="0"
|
:min="0"
|
||||||
:max="10"
|
:max="10"
|
||||||
|
class="max-w-[150px]"
|
||||||
|
wrapper-class="gap-3"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -24,14 +26,19 @@
|
|||||||
<UiNumberInput
|
<UiNumberInput
|
||||||
label="Autres"
|
label="Autres"
|
||||||
v-model="otherQuantity"
|
v-model="otherQuantity"
|
||||||
|
class="max-w-[80px]"
|
||||||
|
wrapper-class="gap-3"
|
||||||
/>
|
/>
|
||||||
</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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -1,183 +1,115 @@
|
|||||||
<template>
|
<template>
|
||||||
<form @submit.prevent="validate">
|
<form>
|
||||||
<div
|
<div class="flex flex-row justify-between gap-x-12 font-bold uppercase mb-8">
|
||||||
class="flex flex-col items-center gap-16">
|
|
||||||
<div
|
|
||||||
class="flex flex-row gap-6 items-center">
|
|
||||||
<div
|
<div
|
||||||
v-for="type in bovineType"
|
v-for="type in bovineTypes"
|
||||||
:key="type.id"
|
:key="type.id"
|
||||||
class="flex flex-row mb-2 gap-6 ">
|
>
|
||||||
<UiNumberInput
|
<UiNumberInput
|
||||||
:label="type.label"
|
:label="type.label"
|
||||||
:code="type.code"
|
:code="type.code"
|
||||||
v-model="bovineQuantities[String(type.id)]"
|
v-model="localQuantities[String(type.id)]"
|
||||||
:disabled="!auth.isAdmin"
|
:disabled="!isAdmin"
|
||||||
:placeholder="0"
|
:placeholder="0"
|
||||||
:min="0"
|
:min="0"
|
||||||
:max="10"
|
:max="10"
|
||||||
|
wrapperClass="w-44 flex-col"
|
||||||
|
inputClass="font-medium"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<UiNumberInput
|
||||||
class=" flex flex-row mb-2 gap-6">
|
label="Autres"
|
||||||
<UiNumberInput
|
v-model="localOtherQuantity"
|
||||||
label="Autres"
|
:disabled="!isAdmin"
|
||||||
v-model="otherQuantity"
|
wrapperClass="w-44 flex-col"
|
||||||
:disabled="!auth.isAdmin"
|
inputClass="font-medium"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<UiButton
|
|
||||||
type="submit"
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
>Valider
|
|
||||||
</UiButton>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type {BovineTypeData} from "~/services/dto/bovine-type-data";
|
import { onMounted, reactive, ref, watch } from 'vue'
|
||||||
import {getBovineTypeList} from "~/services/bovine-type";
|
import { getBovineTypeList } from '~/services/bovine-type'
|
||||||
import {
|
import type { BovineTypeData } from '~/services/dto/bovine-type-data'
|
||||||
createReceptionBovine,
|
import type { ReceptionBovineTypeData } from '~/services/dto/reception-bovine-data'
|
||||||
deleteReceptionBovine,
|
|
||||||
getReceptionBovineList,
|
|
||||||
updateReceptionBovine
|
|
||||||
} from "~/services/reception-bovine";
|
|
||||||
import {computed, onMounted, reactive, ref, watch} from "vue";
|
|
||||||
import {getReception, updateReception} from "~/services/reception";
|
|
||||||
const toast = useToast()
|
|
||||||
const isLoadingBovineType = ref(false)
|
|
||||||
const bovineType = ref<BovineTypeData[]>([])
|
|
||||||
const bovineQuantities = reactive<Record<string, number | null>>({})
|
|
||||||
const otherQuantity = ref<number | null>(0)
|
|
||||||
const auth = useAuthStore()
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
idReception: number
|
modelValue: ReceptionBovineTypeData[]
|
||||||
|
otherQuantity: number | null
|
||||||
|
isAdmin: boolean
|
||||||
}>()
|
}>()
|
||||||
const receptionId = props.idReception
|
|
||||||
const reception = await getReception(receptionId)
|
|
||||||
|
|
||||||
const receptionIri = computed(() =>
|
const emit = defineEmits<{
|
||||||
receptionId ? `/api/receptions/${receptionId}` : null
|
(event: 'update:modelValue', value: ReceptionBovineTypeData[]): void
|
||||||
)
|
(event: 'update:otherQuantity', value: number | null): void
|
||||||
const totalBovines = computed(() => {
|
}>()
|
||||||
const base = Object.values(bovineQuantities).reduce((sum, value) => {
|
|
||||||
return sum + (value ?? 0)
|
|
||||||
}, 0)
|
|
||||||
return base + (otherQuantity.value ?? 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
const loadBovineType = async () => {
|
const bovineTypes = ref<BovineTypeData[]>([])
|
||||||
isLoadingBovineType.value = true
|
const localQuantities = reactive<Record<string, number | null>>({})
|
||||||
|
const localOtherQuantity = ref<number | null>(props.otherQuantity ?? 0)
|
||||||
|
const isSyncing = ref(false)
|
||||||
|
|
||||||
|
function buildEntriesFromLocal(): ReceptionBovineTypeData[] {
|
||||||
|
return bovineTypes.value.map((type) => {
|
||||||
|
const existing = props.modelValue.find((entry) => entry.bovineType.id === type.id)
|
||||||
|
return {
|
||||||
|
id: existing?.id ?? 0,
|
||||||
|
bovineType: type,
|
||||||
|
quantity: localQuantities[String(type.id)] ?? 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncLocalFromProps() {
|
||||||
|
isSyncing.value = true
|
||||||
try {
|
try {
|
||||||
bovineType.value = await getBovineTypeList()
|
for (const key of Object.keys(localQuantities)) {
|
||||||
|
delete localQuantities[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const type of bovineTypes.value) {
|
||||||
|
const existing = props.modelValue.find((entry) => entry.bovineType.id === type.id)
|
||||||
|
localQuantities[String(type.id)] = existing?.quantity ?? 0
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
isLoadingBovineType.value = false
|
isSyncing.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
watch(
|
||||||
await loadBovineType()
|
() => props.otherQuantity,
|
||||||
|
(value) => {
|
||||||
|
localOtherQuantity.value = value ?? 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(localOtherQuantity, (value) => {
|
||||||
|
emit('update:otherQuantity', value ?? 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[() => receptionId, () => bovineType.value],
|
() => props.modelValue,
|
||||||
async ([id, types]) => {
|
() => {
|
||||||
if (!id || !receptionIri.value || types.length === 0) {
|
syncLocalFromProps()
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectionMap: Record<string, number | null> = {}
|
|
||||||
for (const type of types) {
|
|
||||||
selectionMap[String(type.id)] = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const existing = await getReceptionBovineList(receptionIri.value)
|
|
||||||
for (const selection of existing) {
|
|
||||||
const bovineTypeId = String(selection.bovineType.id)
|
|
||||||
selectionMap[bovineTypeId] = selection.quantity ?? 0
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key of Object.keys(bovineQuantities)) {
|
|
||||||
delete bovineQuantities[key]
|
|
||||||
}
|
|
||||||
Object.assign(bovineQuantities, selectionMap)
|
|
||||||
|
|
||||||
const existingOther = reception.bovineDetail
|
|
||||||
const parsedOther =
|
|
||||||
typeof existingOther === 'string' && existingOther.trim() !== ''
|
|
||||||
? Number(existingOther)
|
|
||||||
: 0
|
|
||||||
otherQuantity.value = Number.isFinite(parsedOther) ? parsedOther : 0
|
|
||||||
},
|
},
|
||||||
{immediate: true}
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
async function syncBovineSelections(receptionIri: string) {
|
watch(
|
||||||
const existing = await getReceptionBovineList(receptionIri)
|
localQuantities,
|
||||||
const existingMap = new Map<string, { id: number; quantity: number | null }>()
|
() => {
|
||||||
|
if (isSyncing.value) {
|
||||||
for (const selection of existing) {
|
return
|
||||||
const bovineTypeId = String(selection.bovineType.id)
|
|
||||||
existingMap.set(bovineTypeId, {
|
|
||||||
id: selection.id,
|
|
||||||
quantity: selection.quantity ?? 0
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Supprime les entrées supprimées ou modifiées
|
|
||||||
for (const [bovineTypeId, entry] of existingMap.entries()) {
|
|
||||||
const selectedQuantity = bovineQuantities[bovineTypeId] ?? 0
|
|
||||||
if (!selectedQuantity) {
|
|
||||||
await deleteReceptionBovine(entry.id)
|
|
||||||
existingMap.delete(bovineTypeId)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
emit('update:modelValue', buildEntriesFromLocal())
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
if (selectedQuantity !== entry.quantity) {
|
onMounted(async () => {
|
||||||
await updateReceptionBovine(entry.id, {quantity: selectedQuantity})
|
bovineTypes.value = await getBovineTypeList()
|
||||||
existingMap.set(bovineTypeId, {
|
syncLocalFromProps()
|
||||||
id: entry.id,
|
emit('update:modelValue', buildEntriesFromLocal())
|
||||||
quantity: selectedQuantity
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Crée les entrées manquantes
|
|
||||||
for (const [bovineTypeId, quantity] of Object.entries(bovineQuantities)) {
|
|
||||||
if (!quantity) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (existingMap.has(bovineTypeId)) {
|
|
||||||
// Déjà à jour
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
await createReceptionBovine({
|
|
||||||
reception: receptionIri,
|
|
||||||
bovineType: `/api/bovine_types/${bovineTypeId}`,
|
|
||||||
quantity
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function validate() {
|
|
||||||
// @TODO Ajouter un composable pour le toaster qui gère les key i18n
|
|
||||||
if (totalBovines.value > 52) {
|
|
||||||
toast.error({
|
|
||||||
title: 'Erreur',
|
|
||||||
message: ('Le total des bovins ne peut pas dépasser 52.')
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await syncBovineSelections(receptionIri.value)
|
|
||||||
|
|
||||||
await updateReception(receptionId, {
|
|
||||||
merchandiseType: null,
|
|
||||||
merchandiseDetail: null,
|
|
||||||
bovineDetail: otherQuantity.value ? String(otherQuantity.value) : null,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,33 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<form @submit.prevent="validate">
|
<form>
|
||||||
<div class="flex flex-col items-center gap-16">
|
<div class="flex flex-col">
|
||||||
<div
|
<div class="w-full col-start-1 row-start-1">
|
||||||
class="flex flex-col gap-16 items-center w-full">
|
<UiRadioGroup
|
||||||
<UiTextInput
|
|
||||||
id="merchandise-type"
|
id="merchandise-type"
|
||||||
v-model="selectedMerchandiseTypeId"
|
v-model="selectedMerchandiseTypeId"
|
||||||
label="Type de marchandises"
|
label="Type de marchandises"
|
||||||
:value="reception.merchandiseType?.label"
|
:options="merchandiseTypes.map((type) => ({
|
||||||
wrapper-class="w-[550px]"
|
value: String(type.id),
|
||||||
:disabled="true"
|
label: type.label
|
||||||
|
}))"
|
||||||
|
input-class="accent-primary-700 focus:ring-primary-700"
|
||||||
|
option-label-class="uppercase"
|
||||||
|
wrapper-class="w-full uppercase"
|
||||||
|
group-class="grid grid-cols-[336px_336px_355px_200px] w-[160px_160px_200px_180px] mt-9 mb-7"
|
||||||
|
:disabled="!isAdmin"
|
||||||
/>
|
/>
|
||||||
<div
|
</div>
|
||||||
v-if="merchandiseTypeId && isAutres"
|
|
||||||
class="flex flex-col w-full max-w-[550px]"
|
|
||||||
>
|
|
||||||
<UiTextInput
|
|
||||||
id="merchandise-detail"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
v-model="merchandiseDetail"
|
|
||||||
label="Préciser"
|
|
||||||
placeholder="Précisions complémentaires"
|
|
||||||
:maxlength="255"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="w-full grid grid-cols-[3fr_1fr] gap-12 col-start-2 row-start-1">
|
||||||
<div
|
<div
|
||||||
v-if="merchandiseTypeId && !isGranule"
|
v-if="selectedMerchandiseTypeId && !isGranule"
|
||||||
class="flex gap-4 w-[550px] justify-evenly"
|
class="flex gap-[218px]"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="building in buildings"
|
v-for="building in buildings"
|
||||||
@@ -37,112 +31,174 @@
|
|||||||
v-model="selectedBuildingIds"
|
v-model="selectedBuildingIds"
|
||||||
:value="String(building.id)"
|
:value="String(building.id)"
|
||||||
:label="building.label"
|
:label="building.label"
|
||||||
:disabled="!auth.isAdmin"
|
:disabled="!isAdmin"
|
||||||
label-class="text-xl"
|
input-class="accent-primary-700 focus:ring-primary-700"
|
||||||
|
label-class="text-xl uppercase"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="merchandiseTypeId && isGranule"
|
v-if="selectedMerchandiseTypeId && isAutres"
|
||||||
class="flex flex-col gap-10 w-full max-w-[1100px]"
|
class="flex flex-col justify-self-end max-w-[182px]"
|
||||||
>
|
>
|
||||||
<div class="grid grid-cols-1 gap-10 md:grid-cols-4">
|
<UiTextInput
|
||||||
<div v-for="type in pelletTypes" :key="type.id" class="flex flex-col gap-4">
|
id="merchandise-detail"
|
||||||
<p class="font-bold uppercase">{{ type.label }}</p>
|
:disabled="!isAdmin"
|
||||||
<div
|
v-model="merchandiseDetail"
|
||||||
v-for="building in buildings"
|
placeholder="Préciser"
|
||||||
:key="building.id"
|
:maxlength="255"
|
||||||
class="flex items-center gap-2 text-lg"
|
class="h-6"
|
||||||
>
|
/>
|
||||||
<UiCheckbox
|
</div>
|
||||||
v-model="selectedPelletBuildingIds[String(type.id)]"
|
</div>
|
||||||
:value="String(building.id)"
|
|
||||||
:label="building.label"
|
<div
|
||||||
:disabled="!auth.isAdmin"
|
v-if="selectedMerchandiseTypeId && isGranule"
|
||||||
label-class="text-lg"
|
class="flex flex-col gap-10 w-full col-start-2 row-start-1"
|
||||||
/>
|
>
|
||||||
</div>
|
<div class="grid grid-cols-1 md:grid-cols-[max-content_max-content_max-content_max-content] justify-between">
|
||||||
|
<div v-for="type in pelletTypes" :key="type.id" class="flex flex-col gap-4">
|
||||||
|
<p class="mb-1 font-medium uppercase">{{ type.label }}</p>
|
||||||
|
<div
|
||||||
|
v-for="building in buildings"
|
||||||
|
:key="building.id"
|
||||||
|
class="flex text-lg"
|
||||||
|
>
|
||||||
|
<UiCheckbox
|
||||||
|
v-model="selectedPelletBuildingIds[String(type.id)]"
|
||||||
|
:value="String(building.id)"
|
||||||
|
:label="building.label"
|
||||||
|
:disabled="!isAdmin"
|
||||||
|
input-class="accent-primary-700 focus:ring-primary-700"
|
||||||
|
label-class="text-lg"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<UiButton
|
|
||||||
v-if="auth.isAdmin"
|
|
||||||
type="submit"
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
>Valider
|
|
||||||
</UiButton>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed, onMounted, ref} from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import {getBuildingList} from '~/services/building'
|
import type { BuildingData } from '~/services/dto/building-data'
|
||||||
import {getMerchandiseTypeList} from '~/services/merchandise-type'
|
import type { MerchandiseTypeData } from '~/services/dto/merchandise-type-data'
|
||||||
import type {MerchandiseTypeData} from '~/services/dto/merchandise-type-data'
|
import type { PelletTypeData } from '~/services/dto/pellet-type-data'
|
||||||
import type {BuildingData} from '~/services/dto/building-data'
|
import type { MerchandiseEntryData } from '~/services/dto/reception-data'
|
||||||
import type {PelletTypeData} from '~/services/dto/pellet-type-data'
|
import { getBuildingList } from '~/services/building'
|
||||||
import {getPelletTypeList} from '~/services/pellet-type'
|
import { getMerchandiseTypeList } from '~/services/merchandise-type'
|
||||||
import {
|
import { getPelletTypeList } from '~/services/pellet-type'
|
||||||
createReceptionPelletBuilding,
|
import { MERCHANDISE_TYPE_CODES } from '~/utils/constants'
|
||||||
deleteReceptionPelletBuilding,
|
|
||||||
getReceptionPelletBuildingList
|
const props = defineProps<{
|
||||||
} from '~/services/reception-pellet-building'
|
modelValue: MerchandiseEntryData
|
||||||
import {MERCHANDISE_TYPE_CODES} from '~/utils/constants'
|
isAdmin: boolean
|
||||||
import {getReception, updateReception} from "~/services/reception";
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(event: 'update:modelValue', value: MerchandiseEntryData): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const merchandiseTypes = ref<MerchandiseTypeData[]>([])
|
const merchandiseTypes = ref<MerchandiseTypeData[]>([])
|
||||||
const buildings = ref<BuildingData[]>([])
|
const buildings = ref<BuildingData[]>([])
|
||||||
const pelletTypes = ref<PelletTypeData[]>([])
|
const pelletTypes = ref<PelletTypeData[]>([])
|
||||||
|
|
||||||
const selectedMerchandiseTypeId = ref('')
|
const selectedMerchandiseTypeId = ref('')
|
||||||
const selectedBuildingIds = ref<string[]>([])
|
const selectedBuildingIds = ref<string[]>([])
|
||||||
const selectedPelletBuildingIds = ref<Record<string, string[]>>({})
|
const selectedPelletBuildingIds = ref<Record<string, string[]>>({})
|
||||||
const merchandiseDetail = ref('')
|
const merchandiseDetail = ref('')
|
||||||
const auth = useAuthStore()
|
const isHydrating = ref(false)
|
||||||
const props = defineProps<{
|
const isReady = ref(false)
|
||||||
idReception: number
|
|
||||||
}>()
|
|
||||||
const receptionId = props.idReception
|
|
||||||
const reception = await getReception(receptionId)
|
|
||||||
const merchandiseTypeId = await reception.receptionType?.id
|
|
||||||
|
|
||||||
// Extrait l'ID d'une relation depuis un IRI ou un objet complet.
|
const selectedMerchandiseType = computed(() =>
|
||||||
const getRelationId = (value: unknown): string | null => {
|
merchandiseTypes.value.find((type) => String(type.id) === selectedMerchandiseTypeId.value) ?? null
|
||||||
if (!value) {
|
)
|
||||||
return null
|
const isGranule = computed(
|
||||||
|
() => selectedMerchandiseType.value?.code === MERCHANDISE_TYPE_CODES.GRANULE
|
||||||
|
)
|
||||||
|
const isAutres = computed(
|
||||||
|
() => selectedMerchandiseType.value?.code === MERCHANDISE_TYPE_CODES.AUTRES
|
||||||
|
)
|
||||||
|
|
||||||
|
function clonePelletSelections(value: Record<string, string[]>) {
|
||||||
|
const clone: Record<string, string[]> = {}
|
||||||
|
for (const [key, buildingIds] of Object.entries(value)) {
|
||||||
|
clone[key] = [...buildingIds]
|
||||||
}
|
}
|
||||||
|
return clone
|
||||||
if (typeof value === 'string') {
|
|
||||||
const match = value.match(/\/(\d+)$/)
|
|
||||||
return match ? match[1] : null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === 'object' && 'id' in value) {
|
|
||||||
const record = value as { id?: number | string }
|
|
||||||
if (typeof record.id === 'number') {
|
|
||||||
return String(record.id)
|
|
||||||
}
|
|
||||||
if (typeof record.id === 'string') {
|
|
||||||
return record.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type de marchandise sélectionné dans le select
|
function ensurePelletKeys() {
|
||||||
const selectedMerchandiseType = computed(() =>
|
for (const pelletType of pelletTypes.value) {
|
||||||
merchandiseTypes.value.find((type) => String(type.id) === selectedMerchandiseTypeId.value)
|
const key = String(pelletType.id)
|
||||||
)
|
if (!selectedPelletBuildingIds.value[key]) {
|
||||||
// Indique si le type est "Granulé"
|
selectedPelletBuildingIds.value[key] = []
|
||||||
const isGranule = computed(() => selectedMerchandiseType.value?.code === MERCHANDISE_TYPE_CODES.GRANULE)
|
}
|
||||||
// Indique si le type est "Autres"
|
}
|
||||||
const isAutres = computed(() => selectedMerchandiseType.value?.code === MERCHANDISE_TYPE_CODES.AUTRES)
|
}
|
||||||
|
|
||||||
|
function hydrateFromModelValue(value: MerchandiseEntryData) {
|
||||||
|
isHydrating.value = true
|
||||||
|
try {
|
||||||
|
selectedMerchandiseTypeId.value = value.merchandiseTypeId ?? ''
|
||||||
|
merchandiseDetail.value = value.merchandiseDetail ?? ''
|
||||||
|
selectedBuildingIds.value = [...(value.selectedBuildingIds ?? [])]
|
||||||
|
selectedPelletBuildingIds.value = clonePelletSelections(
|
||||||
|
value.selectedPelletBuildingIds ?? {}
|
||||||
|
)
|
||||||
|
ensurePelletKeys()
|
||||||
|
} finally {
|
||||||
|
isHydrating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitModelValue() {
|
||||||
|
emit('update:modelValue', {
|
||||||
|
merchandiseTypeId: selectedMerchandiseTypeId.value,
|
||||||
|
merchandiseDetail: merchandiseDetail.value,
|
||||||
|
selectedBuildingIds: [...selectedBuildingIds.value],
|
||||||
|
selectedPelletBuildingIds: clonePelletSelections(selectedPelletBuildingIds.value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(value) => {
|
||||||
|
hydrateFromModelValue(value)
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[selectedMerchandiseTypeId, selectedBuildingIds, selectedPelletBuildingIds, merchandiseDetail],
|
||||||
|
() => {
|
||||||
|
if (isHydrating.value || !isReady.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isGranule.value) {
|
||||||
|
if (selectedBuildingIds.value.length > 0) {
|
||||||
|
selectedBuildingIds.value = []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const key of Object.keys(selectedPelletBuildingIds.value)) {
|
||||||
|
if (selectedPelletBuildingIds.value[key].length > 0) {
|
||||||
|
selectedPelletBuildingIds.value[key] = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAutres.value && merchandiseDetail.value !== '') {
|
||||||
|
merchandiseDetail.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
emitModelValue()
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
// Charge les référentiels et hydrate le formulaire depuis la réception
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const [merchandiseTypeList, buildingList, pelletTypeList] = await Promise.all([
|
const [merchandiseTypeList, buildingList, pelletTypeList] = await Promise.all([
|
||||||
getMerchandiseTypeList(),
|
getMerchandiseTypeList(),
|
||||||
@@ -153,106 +209,8 @@ onMounted(async () => {
|
|||||||
buildings.value = buildingList
|
buildings.value = buildingList
|
||||||
pelletTypes.value = pelletTypeList
|
pelletTypes.value = pelletTypeList
|
||||||
|
|
||||||
const currentId = reception.merchandiseType?.id
|
hydrateFromModelValue(props.modelValue)
|
||||||
if (currentId) {
|
isReady.value = true
|
||||||
selectedMerchandiseTypeId.value = String(currentId)
|
emitModelValue()
|
||||||
}
|
|
||||||
merchandiseDetail.value = reception.merchandiseDetail ?? ''
|
|
||||||
|
|
||||||
selectedBuildingIds.value =
|
|
||||||
reception.buildings?.map((building) => String(building.id)) ?? []
|
|
||||||
|
|
||||||
const existingPelletSelections = reception.pelletBuildings ?? []
|
|
||||||
const selectionMap: Record<string, string[]> = {}
|
|
||||||
for (const selection of existingPelletSelections) {
|
|
||||||
// L'API peut renvoyer les relations comme IRI ou comme objets selon le contexte.
|
|
||||||
const pelletTypeId = getRelationId(selection.pelletType)
|
|
||||||
const buildingId = getRelationId(selection.building)
|
|
||||||
if (!pelletTypeId || !buildingId) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (!selectionMap[pelletTypeId]) {
|
|
||||||
selectionMap[pelletTypeId] = []
|
|
||||||
}
|
|
||||||
selectionMap[pelletTypeId].push(buildingId)
|
|
||||||
}
|
|
||||||
for (const pelletType of pelletTypes.value) {
|
|
||||||
const key = String(pelletType.id)
|
|
||||||
if (!selectionMap[key]) {
|
|
||||||
selectionMap[key] = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectedPelletBuildingIds.value = selectionMap
|
|
||||||
})
|
})
|
||||||
// Enregistre les sélections et passe à l'étape suivante
|
|
||||||
async function validate() {
|
|
||||||
|
|
||||||
const receptionIri = `/api/receptions/${reception.id}`
|
|
||||||
|
|
||||||
await updateReception(reception.id, {
|
|
||||||
merchandiseDetail: isAutres.value ? merchandiseDetail.value.trim() : null,
|
|
||||||
buildings: isGranule.value
|
|
||||||
? []
|
|
||||||
: selectedBuildingIds.value.map((id) => `/api/buildings/${id}`),
|
|
||||||
bovineDetail: null,
|
|
||||||
bovinesTypes: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (isGranule.value) {
|
|
||||||
await syncPelletSelections(receptionIri)
|
|
||||||
} else {
|
|
||||||
await clearPelletSelections(receptionIri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Supprime toutes les associations granulés/bâtiments existantes
|
|
||||||
async function clearPelletSelections(receptionIri: string) {
|
|
||||||
const existing = await getReceptionPelletBuildingList(receptionIri)
|
|
||||||
for (const selection of existing) {
|
|
||||||
await deleteReceptionPelletBuilding(selection.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Synchronise les associations granulés/bâtiments avec l'état du formulaire
|
|
||||||
async function syncPelletSelections(receptionIri: string) {
|
|
||||||
const existing = await getReceptionPelletBuildingList(receptionIri)
|
|
||||||
const existingMap = new Map<string, number>()
|
|
||||||
for (const selection of existing) {
|
|
||||||
// Construit la table de correspondance avec des IDs normalisés pour éviter les doublons.
|
|
||||||
const pelletTypeId = getRelationId(selection.pelletType)
|
|
||||||
const buildingId = getRelationId(selection.building)
|
|
||||||
if (!pelletTypeId || !buildingId) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const key = `${pelletTypeId}:${buildingId}`
|
|
||||||
existingMap.set(key, selection.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
const desiredEntries: Array<{ pelletTypeId: string; buildingId: string }> = []
|
|
||||||
for (const [pelletTypeId, buildingIds] of Object.entries(selectedPelletBuildingIds.value)) {
|
|
||||||
for (const buildingId of buildingIds) {
|
|
||||||
desiredEntries.push({pelletTypeId, buildingId})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const desiredKeys = new Set(desiredEntries.map(
|
|
||||||
(entry) => `${entry.pelletTypeId}:${entry.buildingId}`
|
|
||||||
))
|
|
||||||
|
|
||||||
for (const [key, id] of existingMap.entries()) {
|
|
||||||
if (!desiredKeys.has(key)) {
|
|
||||||
await deleteReceptionPelletBuilding(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const entry of desiredEntries) {
|
|
||||||
const key = `${entry.pelletTypeId}:${entry.buildingId}`
|
|
||||||
if (!existingMap.has(key)) {
|
|
||||||
await createReceptionPelletBuilding({
|
|
||||||
reception: receptionIri,
|
|
||||||
pelletType: `/api/pellet_types/${entry.pelletTypeId}`,
|
|
||||||
building: `/api/buildings/${entry.buildingId}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,124 +1,63 @@
|
|||||||
<template>
|
<template>
|
||||||
<form @submit.prevent="validate">
|
<form>
|
||||||
<div class="grid grid-cols-2 gap-x-40 gap-y-8 mb-8">
|
<div class="grid grid-cols-3 gap-x-40 gap-y-8 mb-8">
|
||||||
|
<UiNumberInput
|
||||||
|
:key="localWeight.type"
|
||||||
|
:label="'POIDS'"
|
||||||
|
labelClass="font-bold uppercase text-xl "
|
||||||
|
v-model="localWeight.weight"
|
||||||
|
:disabled="!isAdmin"
|
||||||
|
:min="0"
|
||||||
|
:max="48000"
|
||||||
|
wrapper-class="flex-col"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UiDateInput
|
||||||
|
label="Date pesée"
|
||||||
|
v-model="localWeight.weighedAt"
|
||||||
|
:disabled="!isAdmin"
|
||||||
|
/>
|
||||||
|
|
||||||
<UiNumberInput
|
<UiNumberInput
|
||||||
label="Dsd"
|
label="Dsd"
|
||||||
class="col-start-2"
|
class="col-start-2"
|
||||||
labelClass="font-bold uppercase"
|
labelClass="font-bold uppercase"
|
||||||
v-model="sharedWeightMeta.dsd"
|
v-model="localWeight.dsd"
|
||||||
:disabled="!auth.isAdmin"
|
:disabled="!isAdmin"
|
||||||
/>
|
wrapper-class="flex-col"
|
||||||
<UiDateInput
|
|
||||||
label="Date pesée"
|
|
||||||
v-model="sharedWeightMeta.weighedAt"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-x-40 mb-16">
|
|
||||||
<UiNumberInput
|
|
||||||
v-for="weight in form.weights"
|
|
||||||
:key="weight.type"
|
|
||||||
:label="getWeightLabel(weight.type)"
|
|
||||||
labelClass="font-bold uppercase text-xl"
|
|
||||||
inputClass="w-24"
|
|
||||||
v-model="weight.weight"
|
|
||||||
:wrapper-class="weight.type === 'tare' ? 'col-start-1 row-start-1' : 'col-start-2 row-start-1'"
|
|
||||||
:disabled="!auth.isAdmin"
|
|
||||||
:min="0"
|
|
||||||
:max="48000"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex justify-center">
|
|
||||||
<UiButton
|
|
||||||
v-if="auth.isAdmin"
|
|
||||||
type="submit"
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
|
|
||||||
>
|
|
||||||
Valider
|
|
||||||
</UiButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type {ReceptionFormWeight} from '~/services/dto/reception-data'
|
import type {WeightEntryData} from '~/services/dto/reception-data'
|
||||||
import {getReception} from '~/services/reception'
|
import {reactive, watch} from "vue";
|
||||||
import {updateWeight} from "~/services/weight";
|
|
||||||
import {useAuthStore} from "~/stores/auth";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
idReception: number
|
modelValue: WeightEntryData
|
||||||
|
isAdmin: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const idReception = props.idReception
|
const emit = defineEmits<{
|
||||||
const auth = useAuthStore()
|
(event: 'update:modelValue', value: WeightEntryData): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const form = reactive({
|
const localWeight = reactive<WeightEntryData>({...props.modelValue})
|
||||||
weights: [
|
|
||||||
{id: 0, type: 'tare' as const, weight: 0, dsd: null, weighedAt: null},
|
|
||||||
{id: 0, type: 'gross' as const, weight: 0, dsd: null, weighedAt: null}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
// DSD et date de pesée sont partagés entre tare et gross dans l'UI.
|
|
||||||
const sharedWeightMeta = reactive<{
|
|
||||||
dsd: number | string | null
|
|
||||||
weighedAt: string | null
|
|
||||||
}>({
|
|
||||||
dsd: null,
|
|
||||||
weighedAt: null
|
|
||||||
})
|
|
||||||
|
|
||||||
const getWeightLabel = (type: 'tare' | 'gross'): string => {
|
watch(
|
||||||
return type === 'tare' ? 'Pesée à vide' : 'Pesée à plein'
|
() => props.modelValue,
|
||||||
}
|
(value) => {
|
||||||
|
Object.assign(localWeight, value)
|
||||||
|
},
|
||||||
|
{deep: true}
|
||||||
|
)
|
||||||
|
|
||||||
const hydrateFromReception = (reception: ReceptionFormWeight) => {
|
watch(
|
||||||
// On hydrate chaque ligne par son type (tare/gross), sans dépendre d'un index.
|
localWeight,
|
||||||
for (const receptionWeight of reception.weights) {
|
(value) => {
|
||||||
const formWeight = form.weights.find(weight => weight.type === receptionWeight.type)
|
emit('update:modelValue', {...value})
|
||||||
if (formWeight) {
|
},
|
||||||
Object.assign(formWeight, receptionWeight)
|
{deep: true}
|
||||||
}
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// On récupère une valeur existante pour préremplir les champs partagés.
|
|
||||||
const weightWithMeta = reception.weights.find(weight =>
|
|
||||||
(weight.dsd !== null && weight.dsd !== undefined)
|
|
||||||
|| (weight.weighedAt !== null && weight.weighedAt !== undefined && weight.weighedAt !== '')
|
|
||||||
)
|
|
||||||
|
|
||||||
if (weightWithMeta) {
|
|
||||||
sharedWeightMeta.dsd = weightWithMeta.dsd ?? null
|
|
||||||
sharedWeightMeta.weighedAt = weightWithMeta.weighedAt ?? null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
const reception = await getReception(idReception)
|
|
||||||
hydrateFromReception(reception)
|
|
||||||
})
|
|
||||||
|
|
||||||
async function validate() {
|
|
||||||
const sharedDsd =
|
|
||||||
sharedWeightMeta.dsd === null || sharedWeightMeta.dsd === undefined || sharedWeightMeta.dsd === ''
|
|
||||||
? null
|
|
||||||
: Number(sharedWeightMeta.dsd)
|
|
||||||
const sharedWeighedAt =
|
|
||||||
sharedWeightMeta.weighedAt === null || sharedWeightMeta.weighedAt === undefined || sharedWeightMeta.weighedAt === ''
|
|
||||||
? null
|
|
||||||
: sharedWeightMeta.weighedAt
|
|
||||||
for (const weight of form.weights) {
|
|
||||||
if (weight.id) {
|
|
||||||
await updateWeight(weight.id, {
|
|
||||||
weight: weight.weight,
|
|
||||||
dsd: Number.isFinite(sharedDsd) ? sharedDsd : null,
|
|
||||||
weighedAt: sharedWeighedAt
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -193,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[]>(() => {
|
||||||
@@ -315,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
|
||||||
@@ -441,19 +441,19 @@ 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)
|
||||||
@@ -462,7 +462,7 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
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()
|
||||||
@@ -498,7 +498,7 @@ const buildPayload = () => {
|
|||||||
Math.trunc(rawQuantity)) : 0
|
Math.trunc(rawQuantity)) : 0
|
||||||
|
|
||||||
return {
|
return {
|
||||||
licencePlate: normalizedLicensePlate,
|
licensePlate: normalizedLicensePlate,
|
||||||
shipmentDate: normalizedShipmentDate,
|
shipmentDate: normalizedShipmentDate,
|
||||||
customer: customerIri,
|
customer: customerIri,
|
||||||
truck: truckIri,
|
truck: truckIri,
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :class="wrapperClass">
|
<div :class="wrapperClass">
|
||||||
<label
|
<label
|
||||||
class="flex items-center gap-2 cursor-pointer text-primary-500"
|
class="flex items-center gap-2 cursor-pointer text-primary-700"
|
||||||
:class="labelClass"
|
:class="labelClass"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:checked="checked"
|
:checked="checked"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:class="['cursor-pointer text-primary-500', inputClass]"
|
:class="['h-4 w-4 cursor-pointer text-primary-500', inputClass]"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
>
|
>
|
||||||
<span v-if="label">{{ label }}</span>
|
<span v-if="label">{{ label }}</span>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<label
|
<label
|
||||||
v-if="label"
|
v-if="label"
|
||||||
:for="id"
|
:for="id"
|
||||||
class="font-bold uppercase text-xl text-primary-500"
|
class="font-bold uppercase text-xl text-primary-700"
|
||||||
:class="labelClass"
|
:class="labelClass"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
@@ -14,9 +14,9 @@
|
|||||||
:value="modelValue ?? ''"
|
:value="modelValue ?? ''"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
class="border-b border-black justify-self-start text-xl text-primary-500 py-[6px] uppercase bg-transparent appearance-none h-[34px]"
|
class="border-b border-primary-700 justify-self-start text-xl text-primary-700 py-[6px] uppercase bg-transparent appearance-none h-[34px]"
|
||||||
:class="[
|
:class="[
|
||||||
isEmpty ? 'text-neutral-400' : 'text-black',
|
isEmpty ? 'text-neutral-400' : 'text-primary-700',
|
||||||
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
|
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
|
||||||
inputClass
|
inputClass
|
||||||
]"
|
]"
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
// flex row passer en class wraper class flex col ainsi que le wfull 34
|
||||||
<template>
|
<template>
|
||||||
<div :class="['flex flex-row items-center gap-2', wrapperClass]">
|
<div :class="['flex', wrapperClass]">
|
||||||
<label
|
<label
|
||||||
v-if="label"
|
v-if="label"
|
||||||
:for="id"
|
:for="id"
|
||||||
class="text-xl flex items-center gap-2 text-primary-500"
|
class="text-xl flex items-center gap-2 text-primary-700"
|
||||||
:class="labelClass"
|
:class="labelClass"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
@@ -25,7 +26,7 @@
|
|||||||
:step="step"
|
:step="step"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
class="border-b border-black text-xl bg-transparent w-16 text-primary-500"
|
class="border-b border-primary-700 justify-self-start text-xl text-primary-700 py-[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-text',
|
disabled ? 'cursor-not-allowed' : 'cursor-text',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div :class="['flex flex-col', wrapperClass]">
|
<div :class="['flex flex-col', wrapperClass]">
|
||||||
<label
|
<label
|
||||||
v-if="label"
|
v-if="label"
|
||||||
class="font-bold uppercase text-xl text-primary-500"
|
class="font-bold uppercase text-xl text-primary-700"
|
||||||
:class="labelClass"
|
:class="labelClass"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
v-for="option in options"
|
v-for="option in options"
|
||||||
:key="String(option.value)"
|
:key="String(option.value)"
|
||||||
:for="`${id || 'radio'}-${option.value}`"
|
:for="`${id || 'radio'}-${option.value}`"
|
||||||
class="flex items-center gap-2 text-primary-500"
|
class="flex items-center gap-2 text-primary-700"
|
||||||
:class="itemClass"
|
:class="itemClass"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
:checked="String(modelValue ?? '') === String(option.value)"
|
:checked="String(modelValue ?? '') === String(option.value)"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
class="h-4 w-4 border-slate-300 text-primary-500 focus:ring-primary-500"
|
class="h-4 w-4 border-primary-700/50 text-primary-700 focus:ring-primary-700"
|
||||||
:class="[
|
:class="[
|
||||||
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
|
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
|
||||||
inputClass
|
inputClass
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<label
|
<label
|
||||||
v-if="label"
|
v-if="label"
|
||||||
:for="id"
|
:for="id"
|
||||||
class="font-bold uppercase text-xl text-primary-500"
|
class="font-bold uppercase text-xl text-primary-700"
|
||||||
:class="labelClass"
|
:class="labelClass"
|
||||||
>
|
>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
@@ -13,9 +13,9 @@
|
|||||||
: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 text-primary-500 py-[6px] bg-transparent"
|
class="border-b border-primary-700 justify-self-start text-xl text-primary-700 py-[6px] bg-transparent"
|
||||||
:class="[
|
:class="[
|
||||||
isEmpty ? 'text-neutral-400' : 'text-black',
|
isEmpty ? 'text-neutral-400' : 'text-primary-700',
|
||||||
disabled || loading ? 'cursor-not-allowed' : 'cursor-pointer',
|
disabled || loading ? 'cursor-not-allowed' : 'cursor-pointer',
|
||||||
selectClass
|
selectClass
|
||||||
]"
|
]"
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
v-for="option in options"
|
v-for="option in options"
|
||||||
:key="option.value"
|
:key="option.value"
|
||||||
:value="option.value"
|
:value="option.value"
|
||||||
class="text-black"
|
class="text-primary-700"
|
||||||
>
|
>
|
||||||
{{ option.label }}
|
{{ option.label }}
|
||||||
</option>
|
</option>
|
||||||
|
|||||||
@@ -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)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
definePageMeta({
|
|
||||||
layout: 'default'
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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) })
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
:disabled="!hasCaseId"
|
:disabled="!hasCaseId"
|
||||||
@click="printCaseReport"
|
@click="printCaseReport"
|
||||||
>
|
>
|
||||||
Imprimer case {{ caseId || '-' }}
|
Imprimer
|
||||||
</UiButton>
|
</UiButton>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
|
|
||||||
<form @submit.prevent="validate">
|
<form @submit.prevent="validate">
|
||||||
<div class="flex items-center justify-between mt-12 mb-8 ">
|
<div class="grid grid-cols-2 items-start gap-y-8 gap-x-40 mb-[60px]">
|
||||||
<h1 class="font-bold text-5xl uppercase">Réception {{ receptionLoad?.identificationNumber }}</h1>
|
<div class="flex items-center justify-between gap-10 relative">
|
||||||
</div>
|
<div class="flex flex-row absolute -left-[60px] justify-between">
|
||||||
<div class="grid grid-cols-2 items-start gap-y-8 gap-x-40 mb-12">
|
<Icon @click="router.push('/reception/finish-reception')" name="gg:arrow-left-o" size="44" class="cursor-pointer text-primary-500"/>
|
||||||
|
</div>
|
||||||
|
<h1 class="font-bold text-4xl col-start-1 row-start-1 text-primary-500 uppercase">Réception {{ form.identificationNumber }}</h1>
|
||||||
|
<Icon @click="printReceipt" name="mdi:printer-outline" size="44" class="cursor-pointer text-primary-500"/>
|
||||||
|
</div>
|
||||||
<!-- Nom de l'utilisateur -->
|
<!-- Nom de l'utilisateur -->
|
||||||
<UiSelect
|
<UiSelect
|
||||||
id="reception-user"
|
id="reception-user"
|
||||||
@@ -16,7 +20,7 @@
|
|||||||
label: user.username
|
label: user.username
|
||||||
}))"
|
}))"
|
||||||
:loading="isLoadingUsers"
|
:loading="isLoadingUsers"
|
||||||
wrapper-class="col-start-1 row-start-1"
|
wrapper-class="col-start-1 row-start-2"
|
||||||
/>
|
/>
|
||||||
<!-- Date de réception -->
|
<!-- Date de réception -->
|
||||||
<UiDateInput
|
<UiDateInput
|
||||||
@@ -24,7 +28,20 @@
|
|||||||
:disabled="!auth.isAdmin"
|
:disabled="!auth.isAdmin"
|
||||||
v-model="form.receptionDate"
|
v-model="form.receptionDate"
|
||||||
label="Date de réception"
|
label="Date de réception"
|
||||||
wrapper-class="col-start-1 row-start-2"
|
wrapper-class="col-start-1 row-start-3"
|
||||||
|
/>
|
||||||
|
<!-- type de reception -->
|
||||||
|
<UiSelect
|
||||||
|
id="reception-supplier"
|
||||||
|
v-model="form.receptionTypeId"
|
||||||
|
:disabled="!auth.isAdmin"
|
||||||
|
label="Type de Réception"
|
||||||
|
:options="receptionTypes.map((receptionType) => ({
|
||||||
|
value: String(receptionType.id),
|
||||||
|
label: receptionType.label
|
||||||
|
}))"
|
||||||
|
:loading="isLoadingSuppliers"
|
||||||
|
wrapper-class="col-start-1 row-start-4"
|
||||||
/>
|
/>
|
||||||
<!-- Fournisseur -->
|
<!-- Fournisseur -->
|
||||||
<UiSelect
|
<UiSelect
|
||||||
@@ -37,7 +54,7 @@
|
|||||||
label: supplier.name
|
label: supplier.name
|
||||||
}))"
|
}))"
|
||||||
:loading="isLoadingSuppliers"
|
:loading="isLoadingSuppliers"
|
||||||
wrapper-class="col-start-1 row-start-3"
|
wrapper-class="col-start-1 row-start-5"
|
||||||
/>
|
/>
|
||||||
<!-- Adresse fournisseur -->
|
<!-- Adresse fournisseur -->
|
||||||
<UiSelect
|
<UiSelect
|
||||||
@@ -49,7 +66,7 @@
|
|||||||
label: address.fullAddress
|
label: address.fullAddress
|
||||||
}))"
|
}))"
|
||||||
:disabled="(isLoadingSuppliers || supplierAddresses.length === 0) && !auth.isAdmin"
|
:disabled="(isLoadingSuppliers || supplierAddresses.length === 0) && !auth.isAdmin"
|
||||||
wrapper-class="col-start-1 row-start-4"
|
wrapper-class="col-start-2 row-start-1"
|
||||||
/>
|
/>
|
||||||
<!-- Camion -->
|
<!-- Camion -->
|
||||||
<UiSelect
|
<UiSelect
|
||||||
@@ -62,7 +79,7 @@
|
|||||||
label: truck.name
|
label: truck.name
|
||||||
}))"
|
}))"
|
||||||
:loading="isLoadingTrucks"
|
:loading="isLoadingTrucks"
|
||||||
wrapper-class="col-start-2 row-start-1"
|
wrapper-class="col-start-2 row-start-2"
|
||||||
/>
|
/>
|
||||||
<!-- Transporteur -->
|
<!-- Transporteur -->
|
||||||
<UiSelect
|
<UiSelect
|
||||||
@@ -76,7 +93,7 @@
|
|||||||
}))"
|
}))"
|
||||||
:loading="isLoadingCarriers"
|
:loading="isLoadingCarriers"
|
||||||
select-class="h-[34px]"
|
select-class="h-[34px]"
|
||||||
wrapper-class="col-start-2 row-start-2"
|
wrapper-class="col-start-2 row-start-3"
|
||||||
/>
|
/>
|
||||||
<!-- Chauffeur (LIOT) -->
|
<!-- Chauffeur (LIOT) -->
|
||||||
<UiSelect
|
<UiSelect
|
||||||
@@ -89,7 +106,8 @@
|
|||||||
label: driver.name
|
label: driver.name
|
||||||
}))"
|
}))"
|
||||||
:loading="isLoadingDrivers"
|
:loading="isLoadingDrivers"
|
||||||
wrapper-class="col-start-2 row-start-3"
|
wrapper-class="col-start-2 row-start-5"
|
||||||
|
v-if="isLiotCarrier"
|
||||||
/>
|
/>
|
||||||
<!-- Plaque d'immatriculation -->
|
<!-- Plaque d'immatriculation -->
|
||||||
<div v-if="!isLiotCarrier" class="col-start-2 row-start-4">
|
<div v-if="!isLiotCarrier" class="col-start-2 row-start-4">
|
||||||
@@ -114,80 +132,158 @@
|
|||||||
wrapper-class="col-start-2 row-start-4"
|
wrapper-class="col-start-2 row-start-4"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center mb-2">
|
<div v-if="formIsLoading">
|
||||||
<UiButton
|
<div class="flex justify-evenly gap-y-8 gap-x-41 mb-10 border-b border-primary-500/60">
|
||||||
v-if="auth.isAdmin"
|
|
||||||
type="submit"
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px] mb-16"
|
|
||||||
|
|
||||||
>
|
|
||||||
Enregistrer
|
|
||||||
</UiButton>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-evenly gap-y-8 gap-x-40 mb-8 border-b border-slate-400">
|
|
||||||
<h1
|
<h1
|
||||||
class="font-bold text-3xl uppercase col-start-1 row-start-1 cursor-pointer"
|
class="font-bold text-3xl uppercase px-12 col-start-1 row-start-1 cursor-pointer "
|
||||||
:class="activeTab === 'weights' ? 'underline' : ''"
|
:class="activeTab === 'weights' ? 'border-b-[6px] border-primary-500 text-primary-500' : 'text-primary-500/50'"
|
||||||
@click="activeTab = 'weights'"
|
@click="activeTab = 'weights'"
|
||||||
>
|
>
|
||||||
pesées
|
pesée à plein
|
||||||
</h1>
|
</h1>
|
||||||
<h1
|
<h1
|
||||||
class="font-bold text-3xl uppercase col-start-2 row-start-1 cursor-pointer"
|
class="font-bold text-3xl uppercase col-start-1 row-start-1 px-12 cursor-pointer "
|
||||||
:class="activeTab === 'merchandise' ? 'underline' : ''"
|
:class="activeTab === 'weightsEmpty' ? 'border-b-[6px] border-primary-500 text-primary-500 ' : 'text-primary-500/50'"
|
||||||
|
@click="activeTab = 'weightsEmpty'"
|
||||||
|
>
|
||||||
|
pesée à vide
|
||||||
|
</h1>
|
||||||
|
<h1
|
||||||
|
class="font-bold text-3xl uppercase px-12 col-start-2 row-start-1 cursor-pointer "
|
||||||
|
:class="activeTab === 'merchandise' ? 'border-b-[6px] border-primary-500 text-primary-500' : 'text-primary-500/50'"
|
||||||
@click="activeTab = 'merchandise'"
|
@click="activeTab = 'merchandise'"
|
||||||
>
|
>
|
||||||
{{ isMerchandise ? "Marchandise" : "Bovins" }}
|
{{ isMerchandise ? "Marchandise" : "Bovins" }}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-12 ">
|
||||||
|
<update-weight
|
||||||
|
v-show="activeTab === 'weights'"
|
||||||
|
v-model="grossWeight"
|
||||||
|
v-if="grossWeight"
|
||||||
|
:isAdmin="auth.isAdmin"
|
||||||
|
/>
|
||||||
|
|
||||||
<update-weight
|
<update-weight
|
||||||
v-if="activeTab === 'weights'"
|
v-show="activeTab === 'weightsEmpty'"
|
||||||
:idReception="idReception"
|
v-model="tareWeight"
|
||||||
:disabled="!auth.isAdmin"
|
v-if="tareWeight"
|
||||||
/>
|
:isAdmin="auth.isAdmin"
|
||||||
|
/>
|
||||||
|
|
||||||
<update-merchandise
|
<update-merchandise
|
||||||
v-else-if="activeTab === 'merchandise' && isMerchandise"
|
v-show="activeTab === 'merchandise' && isMerchandise"
|
||||||
:idReception="idReception"
|
v-model="merchandiseForm"
|
||||||
:disabled="!auth.isAdmin"
|
:isAdmin="auth.isAdmin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<update-bovin
|
<update-bovin
|
||||||
v-else
|
v-show="activeTab === 'merchandise' && !isMerchandise"
|
||||||
:idReception="idReception"
|
v-model="bovineEntries"
|
||||||
:disabled="!auth.isAdmin"
|
v-model:otherQuantity="bovineOtherQuantity"
|
||||||
/>
|
:isAdmin="auth.isAdmin"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<UiButton
|
||||||
|
v-if="auth.isAdmin"
|
||||||
|
type="submit"
|
||||||
|
class="inline-flex mb-16 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"
|
||||||
|
>
|
||||||
|
<Icon name="mdi:check" size="28" />
|
||||||
|
Valider
|
||||||
|
</UiButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {useReceptionStore} from '~/stores/reception'
|
import { usePdfPrinter } from '#imports'
|
||||||
import type {UserData} from '~/services/dto/user-data'
|
import { computed } from 'vue'
|
||||||
import {getUsers} from '~/services/auth'
|
import UpdateBovin from '~/components/reception/update-bovin.vue'
|
||||||
import {useAuthStore} from '~/stores/auth'
|
import UpdateMerchandise from '~/components/reception/update-merchandise.vue'
|
||||||
import type {SupplierData} from '~/services/dto/supplier-data'
|
import UpdateWeight from '~/components/reception/update-weight.vue'
|
||||||
import {getSupplierList} from '~/services/supplier'
|
import { getUsers } from '~/services/auth'
|
||||||
import type {TruckData} from '~/services/dto/truck-data'
|
import { getCarrierList } from '~/services/carrier'
|
||||||
import {getTruckList} from '~/services/truck'
|
import type { CarrierData } from '~/services/dto/carrier-data'
|
||||||
import type {CarrierData} from '~/services/dto/carrier-data'
|
import type { DriverData } from '~/services/dto/driver-data'
|
||||||
import {getCarrierList} from '~/services/carrier'
|
import type { ReceptionBovineTypeData } from '~/services/dto/reception-bovine-data'
|
||||||
import type {DriverData} from '~/services/dto/driver-data'
|
import type {
|
||||||
import {getDriverList} from '~/services/driver'
|
MerchandiseEntryData,
|
||||||
import type {VehicleData} from '~/services/dto/vehicle-data'
|
ReceptionData,
|
||||||
import {getVehicleList} from '~/services/vehicle'
|
ReceptionFormData,
|
||||||
import {SUPPLIER_CODE} from "~/utils/constants";
|
WeightEntryData
|
||||||
import {deleteReceptionBovine, getReceptionBovineList} from "~/services/reception-bovine";
|
} from '~/services/dto/reception-data'
|
||||||
import type {ReceptionData, ReceptionFormData} from "~/services/dto/reception-data";
|
import type { ReceptionTypeData } from '~/services/dto/reception-type-data'
|
||||||
import {getReception} from "~/services/reception";
|
import type { SupplierData } from '~/services/dto/supplier-data'
|
||||||
import UpdateWeight from "~/components/reception/update-weight.vue";
|
import type { TruckData } from '~/services/dto/truck-data'
|
||||||
import UpdateMerchandise from "~/components/reception/update-merchandise.vue";
|
import type { UserData } from '~/services/dto/user-data'
|
||||||
import UpdateBovin from "~/components/reception/update-bovin.vue";
|
import type { VehicleData } from '~/services/dto/vehicle-data'
|
||||||
|
import { getDriverList } from '~/services/driver'
|
||||||
|
import {
|
||||||
|
createReceptionBovine,
|
||||||
|
deleteReceptionBovine,
|
||||||
|
getReceptionBovineList,
|
||||||
|
updateReceptionBovine
|
||||||
|
} from '~/services/reception-bovine'
|
||||||
|
import {
|
||||||
|
createReceptionPelletBuilding,
|
||||||
|
deleteReceptionPelletBuilding,
|
||||||
|
getReceptionPelletBuildingList
|
||||||
|
} from '~/services/reception-pellet-building'
|
||||||
|
import { getReception, updateReception } from '~/services/reception'
|
||||||
|
import { getReceptionTypeList } from '~/services/reception-type'
|
||||||
|
import { getSupplierList } from '~/services/supplier'
|
||||||
|
import { getTruckList } from '~/services/truck'
|
||||||
|
import { getVehicleList } from '~/services/vehicle'
|
||||||
|
import { createWeight, updateWeight } from '~/services/weight'
|
||||||
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
import { useReceptionStore } from '~/stores/reception'
|
||||||
|
import { RECEPTION_TYPE_CODES, SUPPLIER_CODE } from '~/utils/constants'
|
||||||
|
|
||||||
const activeTab = ref<'weights' | 'merchandise'>('weights')
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
const auth = useAuthStore()
|
||||||
|
const authStore = useAuthStore()
|
||||||
const receptionStore = useReceptionStore()
|
const receptionStore = useReceptionStore()
|
||||||
|
const { printPdf } = usePdfPrinter()
|
||||||
|
|
||||||
|
const activeTab = ref<'weightsEmpty' | 'weights' | 'merchandise'>('weights')
|
||||||
|
const grossWeight = ref<WeightEntryData>(createEmptyWeightEntry('gross'))
|
||||||
|
const tareWeight = ref<WeightEntryData>(createEmptyWeightEntry('tare'))
|
||||||
|
const bovineEntries = ref<ReceptionBovineTypeData[]>([])
|
||||||
|
const bovineOtherQuantity = ref<number | null>(0)
|
||||||
|
const merchandiseForm = ref<MerchandiseEntryData>({
|
||||||
|
merchandiseTypeId: '',
|
||||||
|
merchandiseDetail: '',
|
||||||
|
selectedBuildingIds: [],
|
||||||
|
selectedPelletBuildingIds: {}
|
||||||
|
})
|
||||||
|
const allowAnyLicensePlate = ref(false)
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const users = ref<UserData[]>([])
|
||||||
|
const isLoadingUsers = ref(false)
|
||||||
|
const isLoadingTypes = ref(false)
|
||||||
|
const suppliers = ref<SupplierData[]>([])
|
||||||
|
const receptionTypes = ref<ReceptionTypeData[]>([])
|
||||||
|
const isLoadingSuppliers = ref(false)
|
||||||
|
const trucks = ref<TruckData[]>([])
|
||||||
|
const isLoadingTrucks = ref(false)
|
||||||
|
const carriers = ref<CarrierData[]>([])
|
||||||
|
const isLoadingCarriers = ref(false)
|
||||||
|
const drivers = ref<DriverData[]>([])
|
||||||
|
const isLoadingDrivers = ref(false)
|
||||||
|
const vehicles = ref<VehicleData[]>([])
|
||||||
|
const isLoadingVehicles = ref(false)
|
||||||
|
const formIsLoading = ref(false)
|
||||||
|
const isMerchandise = ref(false)
|
||||||
|
const isHydrating = ref(false)
|
||||||
|
|
||||||
|
const idReception = Number(route.params.id)
|
||||||
|
|
||||||
const form = reactive<ReceptionFormData>({
|
const form = reactive<ReceptionFormData>({
|
||||||
|
identificationNumber: null,
|
||||||
licensePlate: '',
|
licensePlate: '',
|
||||||
receptionDate: new Date().toISOString().slice(0, 10),
|
receptionDate: new Date().toISOString().slice(0, 10),
|
||||||
receptionTypeId: '',
|
receptionTypeId: '',
|
||||||
@@ -199,39 +295,11 @@ const form = reactive<ReceptionFormData>({
|
|||||||
driverId: '',
|
driverId: '',
|
||||||
vehicleId: ''
|
vehicleId: ''
|
||||||
})
|
})
|
||||||
const allowAnyLicensePlate = ref(false)
|
|
||||||
const isLoading = ref(false)
|
|
||||||
const users = ref<UserData[]>([])
|
|
||||||
const isLoadingUsers = ref(false)
|
|
||||||
const suppliers = ref<SupplierData[]>([])
|
|
||||||
const isLoadingSuppliers = ref(false)
|
|
||||||
const trucks = ref<TruckData[]>([])
|
|
||||||
const isLoadingTrucks = ref(false)
|
|
||||||
const carriers = ref<CarrierData[]>([])
|
|
||||||
const isLoadingCarriers = ref(false)
|
|
||||||
const drivers = ref<DriverData[]>([])
|
|
||||||
const isLoadingDrivers = ref(false)
|
|
||||||
const vehicles = ref<VehicleData[]>([])
|
|
||||||
const isLoadingVehicles = ref(false)
|
|
||||||
const authStore = useAuthStore()
|
|
||||||
|
|
||||||
// Empêche les watchers de reset des champs pendant le remplissage initial
|
|
||||||
const isHydrating = ref(false)
|
|
||||||
const route = useRoute()
|
|
||||||
const idReception = Number(route.params.id)
|
|
||||||
const receptionLoad = await getReception(idReception)
|
|
||||||
const receptionType = receptionLoad.receptionType
|
|
||||||
const auth = useAuthStore()
|
|
||||||
const isBtWeight = ref(true)
|
|
||||||
const isMerchandise = ref(receptionType.code === 'MARCHANDISES')
|
|
||||||
|
|
||||||
// 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
|
||||||
)
|
)
|
||||||
// Indique si le transporteur est LIOT
|
|
||||||
const isLiotCarrier = computed(() => selectedCarrier.value?.code === SUPPLIER_CODE.LIOT)
|
const isLiotCarrier = computed(() => selectedCarrier.value?.code === SUPPLIER_CODE.LIOT)
|
||||||
// Adresses disponibles pour le fournisseur sélectionné
|
|
||||||
const supplierAddresses = computed(() => {
|
const supplierAddresses = computed(() => {
|
||||||
const supplierId = Number(form.supplierId)
|
const supplierId = Number(form.supplierId)
|
||||||
if (!Number.isFinite(supplierId)) {
|
if (!Number.isFinite(supplierId)) {
|
||||||
@@ -239,14 +307,12 @@ const supplierAddresses = computed(() => {
|
|||||||
}
|
}
|
||||||
return suppliers.value.find((supplier) => supplier.id === supplierId)?.addresses ?? []
|
return suppliers.value.find((supplier) => supplier.id === supplierId)?.addresses ?? []
|
||||||
})
|
})
|
||||||
// Chauffeurs filtrés par transporteur (LIOT)
|
|
||||||
const filteredDrivers = computed<DriverData[]>(() => {
|
const filteredDrivers = computed<DriverData[]>(() => {
|
||||||
if (!form.carrierId) {
|
if (!form.carrierId) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return drivers.value.filter((driver) => String(driver.carrier?.id) === form.carrierId)
|
return drivers.value.filter((driver) => String(driver.carrier?.id) === form.carrierId)
|
||||||
})
|
})
|
||||||
// Véhicules filtrés par transporteur + type de camion
|
|
||||||
const filteredVehicles = computed<VehicleData[]>(() => {
|
const filteredVehicles = computed<VehicleData[]>(() => {
|
||||||
if (!form.carrierId) {
|
if (!form.carrierId) {
|
||||||
return []
|
return []
|
||||||
@@ -258,19 +324,102 @@ const filteredVehicles = computed<VehicleData[]>(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Supprime les données bovines si on change de type de réception
|
watch(
|
||||||
const clearReceptionBovines = async (receptionIri: string) => {
|
() => idReception,
|
||||||
|
async (id) => {
|
||||||
|
if (id === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isLoading.value = true
|
||||||
|
try {
|
||||||
|
const reception = await getReception(id)
|
||||||
|
hydrateFromReception(reception)
|
||||||
|
await loadBovineEntries(id)
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{immediate: true}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => form.receptionTypeId,
|
||||||
|
() => {
|
||||||
|
const receptionType = receptionTypes.value.find((type) => String(type.id) === form.receptionTypeId) ?? null
|
||||||
|
isMerchandise.value = receptionType?.code === RECEPTION_TYPE_CODES.MERCHANDISES
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function createEmptyWeightEntry(type: 'gross' | 'tare'): WeightEntryData {
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
dsd: null,
|
||||||
|
weight: null,
|
||||||
|
weighedAt: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearReceptionBovines(receptionId: number) {
|
||||||
|
const receptionIri = `/api/receptions/${receptionId}`
|
||||||
const existing = await getReceptionBovineList(receptionIri)
|
const existing = await getReceptionBovineList(receptionIri)
|
||||||
for (const selection of existing) {
|
for (const selection of existing) {
|
||||||
await deleteReceptionBovine(selection.id)
|
await deleteReceptionBovine(selection.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hydrateFromUser = (reception: ReceptionData | null) => {
|
async function loadBovineEntries(receptionId: number) {
|
||||||
|
const receptionIri = `/api/receptions/${receptionId}`
|
||||||
|
bovineEntries.value = await getReceptionBovineList(receptionIri)
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncMerchandiseFlag() {
|
||||||
|
const receptionType =
|
||||||
|
receptionTypes.value.find((type) => String(type.id) === form.receptionTypeId) ?? null
|
||||||
|
isMerchandise.value = receptionType?.code === RECEPTION_TYPE_CODES.MERCHANDISES
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRelationId(value: unknown): string | null {
|
||||||
|
if (!value) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const match = value.match(/\/(\d+)$/)
|
||||||
|
return match ? match[1] : null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'object' && 'id' in value) {
|
||||||
|
const relation = value as { id?: number | string }
|
||||||
|
if (typeof relation.id === 'number') {
|
||||||
|
return String(relation.id)
|
||||||
|
}
|
||||||
|
if (typeof relation.id === 'string') {
|
||||||
|
return relation.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearReceptionMerchandise(receptionId: number) {
|
||||||
|
const receptionIri = `/api/receptions/${receptionId}`
|
||||||
|
const existing = await getReceptionPelletBuildingList(receptionIri)
|
||||||
|
for (const selection of existing) {
|
||||||
|
await deleteReceptionPelletBuilding(selection.id)
|
||||||
|
}
|
||||||
|
await updateReception(receptionId, {
|
||||||
|
merchandiseType: null,
|
||||||
|
merchandiseDetail: null,
|
||||||
|
buildings: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function hydrateFromReception(reception: ReceptionData | null) {
|
||||||
if (!reception) {
|
if (!reception) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isHydrating.value = true
|
isHydrating.value = true
|
||||||
|
form.identificationNumber = reception?.identificationNumber ?? ''
|
||||||
form.licensePlate = reception?.licensePlate ?? ''
|
form.licensePlate = reception?.licensePlate ?? ''
|
||||||
form.receptionDate = reception?.receptionDate ?? new Date().toISOString().slice(0, 10)
|
form.receptionDate = reception?.receptionDate ?? new Date().toISOString().slice(0, 10)
|
||||||
form.userId = reception?.user?.id
|
form.userId = reception?.user?.id
|
||||||
@@ -291,28 +440,44 @@ const hydrateFromUser = (reception: ReceptionData | null) => {
|
|||||||
form.driverId = reception?.driver?.id
|
form.driverId = reception?.driver?.id
|
||||||
? String(reception.driver.id)
|
? String(reception.driver.id)
|
||||||
: ''
|
: ''
|
||||||
|
form.receptionTypeId = reception?.receptionType?.id
|
||||||
|
? String(reception.receptionType.id)
|
||||||
|
: ''
|
||||||
|
|
||||||
|
const selectionMap: Record<string, string[]> = {}
|
||||||
|
for (const selection of reception?.pelletBuildings ?? []) {
|
||||||
|
const pelletTypeId = getRelationId(selection.pelletType)
|
||||||
|
const buildingId = getRelationId(selection.building)
|
||||||
|
if (!pelletTypeId || !buildingId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!selectionMap[pelletTypeId]) {
|
||||||
|
selectionMap[pelletTypeId] = []
|
||||||
|
}
|
||||||
|
selectionMap[pelletTypeId].push(buildingId)
|
||||||
|
}
|
||||||
|
|
||||||
|
merchandiseForm.value = {
|
||||||
|
merchandiseTypeId: reception?.merchandiseType?.id ? String(reception.merchandiseType.id) : '',
|
||||||
|
merchandiseDetail: reception?.merchandiseDetail ?? '',
|
||||||
|
selectedBuildingIds: reception?.buildings?.map((building) => String(building.id)) ?? [],
|
||||||
|
selectedPelletBuildingIds: selectionMap
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedOther =
|
||||||
|
typeof reception?.bovineDetail === 'string' && reception.bovineDetail.trim() !== ''
|
||||||
|
? Number(reception.bovineDetail)
|
||||||
|
: 0
|
||||||
|
bovineOtherQuantity.value = Number.isFinite(parsedOther) ? parsedOther : 0
|
||||||
|
const gross = reception.weights?.find((weight) => weight.type === 'gross') ?? null
|
||||||
|
const tare = reception.weights?.find((weight) => weight.type === 'tare') ?? null
|
||||||
|
grossWeight.value = gross ? { ...gross } : createEmptyWeightEntry('gross')
|
||||||
|
tareWeight.value = tare ? { ...tare } : createEmptyWeightEntry('tare')
|
||||||
isHydrating.value = false
|
isHydrating.value = false
|
||||||
|
syncMerchandiseFlag()
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
async function loadUsers() {
|
||||||
() => idReception,
|
|
||||||
async (id) => {
|
|
||||||
if (id === null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
isLoading.value = true
|
|
||||||
try {
|
|
||||||
const user = await getReception(id)
|
|
||||||
hydrateFromUser(user)
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Charge la liste des users pour le select
|
|
||||||
const loadUsers = async () => {
|
|
||||||
isLoadingUsers.value = true
|
isLoadingUsers.value = true
|
||||||
try {
|
try {
|
||||||
users.value = await getUsers()
|
users.value = await getUsers()
|
||||||
@@ -321,8 +486,7 @@ const loadUsers = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charge la liste des fournisseurs pour le select
|
async function loadSuppliers() {
|
||||||
const loadSuppliers = async () => {
|
|
||||||
isLoadingSuppliers.value = true
|
isLoadingSuppliers.value = true
|
||||||
try {
|
try {
|
||||||
suppliers.value = await getSupplierList()
|
suppliers.value = await getSupplierList()
|
||||||
@@ -331,8 +495,16 @@ const loadSuppliers = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charge la liste des camions pour le select
|
async function loadTypes() {
|
||||||
const loadTrucks = async () => {
|
isLoadingTypes.value = true
|
||||||
|
try {
|
||||||
|
receptionTypes.value = await getReceptionTypeList()
|
||||||
|
} finally {
|
||||||
|
isLoadingSuppliers.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTrucks() {
|
||||||
isLoadingTrucks.value = true
|
isLoadingTrucks.value = true
|
||||||
try {
|
try {
|
||||||
trucks.value = await getTruckList()
|
trucks.value = await getTruckList()
|
||||||
@@ -341,8 +513,7 @@ const loadTrucks = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charge la liste des transporteurs pour le select
|
async function loadCarriers() {
|
||||||
const loadCarriers = async () => {
|
|
||||||
isLoadingCarriers.value = true
|
isLoadingCarriers.value = true
|
||||||
try {
|
try {
|
||||||
carriers.value = await getCarrierList()
|
carriers.value = await getCarrierList()
|
||||||
@@ -351,8 +522,7 @@ const loadCarriers = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charge la liste des chauffeurs pour le select
|
async function loadDrivers() {
|
||||||
const loadDrivers = async () => {
|
|
||||||
isLoadingDrivers.value = true
|
isLoadingDrivers.value = true
|
||||||
try {
|
try {
|
||||||
drivers.value = await getDriverList()
|
drivers.value = await getDriverList()
|
||||||
@@ -361,8 +531,7 @@ const loadDrivers = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charge la liste des véhicules pour le select
|
async function loadVehicles() {
|
||||||
const loadVehicles = async () => {
|
|
||||||
isLoadingVehicles.value = true
|
isLoadingVehicles.value = true
|
||||||
try {
|
try {
|
||||||
vehicles.value = await getVehicleList()
|
vehicles.value = await getVehicleList()
|
||||||
@@ -371,8 +540,7 @@ const loadVehicles = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// On met le user connecté par défaut dans le select
|
function setDefaultUser() {
|
||||||
const setDefaultUser = () => {
|
|
||||||
if (form.userId) {
|
if (form.userId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -381,8 +549,253 @@ const setDefaultUser = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// On récupère toutes les données des selects au chargement du composant
|
async function printReceipt() {
|
||||||
|
if (!import.meta.client || !Number.isFinite(idReception) || idReception <= 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const supplierName =
|
||||||
|
suppliers.value.find((supplier) => String(supplier.id) === form.supplierId)?.name ??
|
||||||
|
'fournisseur'
|
||||||
|
const filename = `${form.identificationNumber || idReception}_${supplierName}_${form.licensePlate || 'immat'}.pdf`
|
||||||
|
await printPdf(`/receptions/${idReception}/receipt`, filename)
|
||||||
|
|
||||||
|
// Laisse le temps a la boite de dialogue d'impression de s'ouvrir.
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 600))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveWeightEntry(entry: WeightEntryData) {
|
||||||
|
if (!idReception || entry.weight === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
type: entry.type,
|
||||||
|
dsd: entry.dsd ?? null,
|
||||||
|
weight: entry.weight,
|
||||||
|
weighedAt: entry.weighedAt ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.id) {
|
||||||
|
await updateWeight(entry.id, payload)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await createWeight({
|
||||||
|
reception: `api/receptions/${idReception}`,
|
||||||
|
...payload
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async function saveBovineEntry(entry: ReceptionBovineTypeData) {
|
||||||
|
if (!idReception || !entry.bovineType || entry.quantity === null || entry.quantity <= 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
quantity: entry.quantity
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.id) {
|
||||||
|
await updateReceptionBovine(entry.id, payload)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await createReceptionBovine({
|
||||||
|
reception: `/api/receptions/${idReception}`,
|
||||||
|
bovineType: `/api/bovine_types/${entry.bovineType.id}`,
|
||||||
|
...payload
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTotalBovines() {
|
||||||
|
const totalTypes = bovineEntries.value.reduce(
|
||||||
|
(sum, entry) => sum + (entry.quantity ?? 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
return totalTypes +
|
||||||
|
(bovineOtherQuantity.value ?? 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncBovineEntries() {
|
||||||
|
if (!idReception) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const receptionIri = `/api/receptions/${idReception}`
|
||||||
|
const existing = await getReceptionBovineList(receptionIri)
|
||||||
|
const currentPositive = bovineEntries.value.filter(
|
||||||
|
(entry) => (entry.quantity ?? 0) > 0
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const existingEntry of existing) {
|
||||||
|
const stillPresent = currentPositive.some(
|
||||||
|
(entry) => entry.bovineType.id === existingEntry.bovineType.id
|
||||||
|
)
|
||||||
|
if (!stillPresent) {
|
||||||
|
await deleteReceptionBovine(existingEntry.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of currentPositive) {
|
||||||
|
await saveBovineEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveMerchandiseEntry(
|
||||||
|
receptionIri: string,
|
||||||
|
pelletTypeId: string,
|
||||||
|
buildingId: string,
|
||||||
|
existingKeys: Set<string>
|
||||||
|
) {
|
||||||
|
const key = `${pelletTypeId}:${buildingId}`
|
||||||
|
if (existingKeys.has(key)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await createReceptionPelletBuilding({
|
||||||
|
reception: receptionIri,
|
||||||
|
pelletType: `/api/pellet_types/${pelletTypeId}`,
|
||||||
|
building: `/api/buildings/${buildingId}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncMerchandiseEntries() {
|
||||||
|
if (!idReception) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const receptionIri = `/api/receptions/${idReception}`
|
||||||
|
const existing = await getReceptionPelletBuildingList(receptionIri)
|
||||||
|
const existingMap = new Map<string, number>()
|
||||||
|
for (const selection of existing) {
|
||||||
|
const pelletTypeId = getRelationId(selection.pelletType)
|
||||||
|
const buildingId = getRelationId(selection.building)
|
||||||
|
if (!pelletTypeId || !buildingId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
existingMap.set(`${pelletTypeId}:${buildingId}`, selection.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const desiredEntries: Array<{ pelletTypeId: string; buildingId: string }> = []
|
||||||
|
for (const [pelletTypeId, buildingIds] of Object.entries(
|
||||||
|
merchandiseForm.value.selectedPelletBuildingIds
|
||||||
|
)) {
|
||||||
|
for (const buildingId of buildingIds) {
|
||||||
|
desiredEntries.push({ pelletTypeId, buildingId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const desiredKeys = new Set(
|
||||||
|
desiredEntries.map((entry) => `${entry.pelletTypeId}:${entry.buildingId}`)
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const [key, selectionId] of existingMap.entries()) {
|
||||||
|
if (!desiredKeys.has(key)) {
|
||||||
|
await deleteReceptionPelletBuilding(selectionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingKeys = new Set(existingMap.keys())
|
||||||
|
for (const entry of desiredEntries) {
|
||||||
|
await saveMerchandiseEntry(
|
||||||
|
receptionIri,
|
||||||
|
entry.pelletTypeId,
|
||||||
|
entry.buildingId,
|
||||||
|
existingKeys
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function validate() {
|
||||||
|
const normalizedLicensePlate = form.licensePlate.trim()
|
||||||
|
const normalizedReceptionDate = form.receptionDate.trim()
|
||||||
|
const normalizedReceptionTypeId = form.receptionTypeId.trim()
|
||||||
|
const normalizedUserId = form.userId.trim()
|
||||||
|
const normalizedSupplierId = form.supplierId.trim()
|
||||||
|
const normalizedAddressId = form.addressId.trim()
|
||||||
|
const normalizedTruckId = form.truckId.trim()
|
||||||
|
const normalizedCarrierId = form.carrierId.trim()
|
||||||
|
const normalizedDriverId = form.driverId.trim()
|
||||||
|
const userIri = normalizedUserId
|
||||||
|
? `/api/users/${normalizedUserId}`
|
||||||
|
: null
|
||||||
|
const supplierIri = normalizedSupplierId
|
||||||
|
? `/api/suppliers/${normalizedSupplierId}`
|
||||||
|
: null
|
||||||
|
const addressIri = normalizedAddressId
|
||||||
|
? `/api/addresses/${normalizedAddressId}`
|
||||||
|
: null
|
||||||
|
const truckIri = normalizedTruckId
|
||||||
|
? `/api/trucks/${normalizedTruckId}`
|
||||||
|
: null
|
||||||
|
const carrierIri = normalizedCarrierId
|
||||||
|
? `/api/carriers/${normalizedCarrierId}`
|
||||||
|
: null
|
||||||
|
const driverIri = normalizedDriverId
|
||||||
|
? `/api/drivers/${normalizedDriverId}`
|
||||||
|
: null
|
||||||
|
const typeIri = normalizedReceptionTypeId
|
||||||
|
? `/api/reception_types/${normalizedReceptionTypeId}`
|
||||||
|
: null
|
||||||
|
|
||||||
|
const basePayload = {
|
||||||
|
licensePlate: normalizedLicensePlate,
|
||||||
|
receptionDate: normalizedReceptionDate,
|
||||||
|
receptionType: typeIri,
|
||||||
|
user: userIri,
|
||||||
|
supplier: supplierIri,
|
||||||
|
address: addressIri,
|
||||||
|
truck: truckIri,
|
||||||
|
carrier: carrierIri
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
...basePayload,
|
||||||
|
...(isLiotCarrier.value && driverIri ? { driver: driverIri } : {}),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idReception) {
|
||||||
|
await receptionStore.updateReception(idReception, {
|
||||||
|
...payload
|
||||||
|
})
|
||||||
|
await saveWeightEntry(grossWeight.value)
|
||||||
|
await saveWeightEntry(tareWeight.value)
|
||||||
|
|
||||||
|
if (isMerchandise.value) {
|
||||||
|
await clearReceptionBovines(idReception)
|
||||||
|
await updateReception(idReception, {
|
||||||
|
merchandiseType: merchandiseForm.value.merchandiseTypeId
|
||||||
|
? `/api/merchandise_types/${merchandiseForm.value.merchandiseTypeId}`
|
||||||
|
: null,
|
||||||
|
merchandiseDetail: merchandiseForm.value.merchandiseDetail.trim() || null,
|
||||||
|
buildings: merchandiseForm.value.selectedBuildingIds.map(
|
||||||
|
(buildingId) => `/api/buildings/${buildingId}`
|
||||||
|
),
|
||||||
|
bovineDetail: null,
|
||||||
|
bovinesTypes: null
|
||||||
|
})
|
||||||
|
await syncMerchandiseEntries()
|
||||||
|
} else {
|
||||||
|
if (getTotalBovines() > 52) {
|
||||||
|
// toast/erreur UI
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await clearReceptionMerchandise(idReception)
|
||||||
|
await syncBovineEntries()
|
||||||
|
await updateReception(idReception, {
|
||||||
|
bovineDetail: bovineOtherQuantity.value ? String(bovineOtherQuantity.value) : null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshedReception = await getReception(idReception)
|
||||||
|
hydrateFromReception(refreshedReception)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
await loadTypes()
|
||||||
|
syncMerchandiseFlag()
|
||||||
|
formIsLoading.value = true
|
||||||
await loadUsers()
|
await loadUsers()
|
||||||
await loadSuppliers()
|
await loadSuppliers()
|
||||||
await loadTrucks()
|
await loadTrucks()
|
||||||
@@ -393,7 +806,6 @@ onMounted(async () => {
|
|||||||
setDefaultUser()
|
setDefaultUser()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Ajuste driver/vehicle quand le transporteur change (logique LIOT)
|
|
||||||
watch(
|
watch(
|
||||||
() => [form.supplierId, form.addressId, suppliers.value],
|
() => [form.supplierId, form.addressId, suppliers.value],
|
||||||
() => {
|
() => {
|
||||||
@@ -419,10 +831,9 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{immediate: true}
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
// Valide/auto-sélectionne le véhicule selon camion + transporteur (LIOT)
|
|
||||||
watch(
|
watch(
|
||||||
() => form.carrierId,
|
() => form.carrierId,
|
||||||
() => {
|
() => {
|
||||||
@@ -446,10 +857,9 @@ watch(
|
|||||||
form.vehicleId = String(filteredVehicles.value[0].id)
|
form.vehicleId = String(filteredVehicles.value[0].id)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{immediate: true}
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
// Récupère la plaque depuis le véhicule choisi (LIOT)
|
|
||||||
watch(
|
watch(
|
||||||
() => [form.truckId, form.carrierId, vehicles.value],
|
() => [form.truckId, form.carrierId, vehicles.value],
|
||||||
() => {
|
() => {
|
||||||
@@ -470,10 +880,9 @@ watch(
|
|||||||
form.vehicleId = ''
|
form.vehicleId = ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{immediate: true}
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
// Auto-renseigne le véhicule si la plaque correspond (LIOT)
|
|
||||||
watch(
|
watch(
|
||||||
() => [form.vehicleId, form.carrierId, vehicles.value],
|
() => [form.vehicleId, form.carrierId, vehicles.value],
|
||||||
() => {
|
() => {
|
||||||
@@ -508,60 +917,4 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Valide le formulaire et crée/met à jour la réception
|
|
||||||
async function validate() {
|
|
||||||
const normalizedLicensePlate = form.licensePlate.trim()
|
|
||||||
const normalizedReceptionDate = form.receptionDate.trim()
|
|
||||||
const normalizedUserId = form.userId.trim()
|
|
||||||
const normalizedSupplierId = form.supplierId.trim()
|
|
||||||
const normalizedAddressId = form.addressId.trim()
|
|
||||||
const normalizedTruckId = form.truckId.trim()
|
|
||||||
const normalizedCarrierId = form.carrierId.trim()
|
|
||||||
const normalizedDriverId = form.driverId.trim()
|
|
||||||
const userIri = normalizedUserId
|
|
||||||
? `/api/users/${normalizedUserId}`
|
|
||||||
: null
|
|
||||||
const supplierIri = normalizedSupplierId
|
|
||||||
? `/api/suppliers/${normalizedSupplierId}`
|
|
||||||
: null
|
|
||||||
const addressIri = normalizedAddressId
|
|
||||||
? `/api/addresses/${normalizedAddressId}`
|
|
||||||
: null
|
|
||||||
const truckIri = normalizedTruckId
|
|
||||||
? `/api/trucks/${normalizedTruckId}`
|
|
||||||
: null
|
|
||||||
const carrierIri = normalizedCarrierId
|
|
||||||
? `/api/carriers/${normalizedCarrierId}`
|
|
||||||
: null
|
|
||||||
const driverIri = normalizedDriverId
|
|
||||||
? `/api/drivers/${normalizedDriverId}`
|
|
||||||
: null
|
|
||||||
|
|
||||||
const basePayload = {
|
|
||||||
licensePlate: normalizedLicensePlate,
|
|
||||||
receptionDate: normalizedReceptionDate,
|
|
||||||
user: userIri,
|
|
||||||
supplier: supplierIri,
|
|
||||||
address: addressIri,
|
|
||||||
truck: truckIri,
|
|
||||||
carrier: carrierIri
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
...basePayload,
|
|
||||||
...(isLiotCarrier.value && driverIri ? {driver: driverIri} : {})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idReception) {
|
|
||||||
const updated = await receptionStore.updateReception(idReception, {
|
|
||||||
...payload
|
|
||||||
})
|
|
||||||
if (updated) {
|
|
||||||
await router.push(`/reception/update/${updated.id}`)
|
|
||||||
}
|
|
||||||
router.push("/reception/finish-reception")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -41,9 +41,18 @@ export interface WeightEntryData {
|
|||||||
weighedAt: string | null
|
weighedAt: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MerchandiseEntryData {
|
||||||
|
merchandiseTypeId: string
|
||||||
|
merchandiseDetail: string
|
||||||
|
selectedBuildingIds: string[]
|
||||||
|
selectedPelletBuildingIds: Record<string, string[]>
|
||||||
|
}
|
||||||
|
|
||||||
export interface WeightFormData {
|
export interface WeightFormData {
|
||||||
id: number
|
id: number
|
||||||
weight: number
|
weight: number
|
||||||
|
weighedAt : string
|
||||||
|
dsd: number
|
||||||
type: 'gross' | 'tare'
|
type: 'gross' | 'tare'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +78,7 @@ export type ReceptionPayload = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type ReceptionFormData = {
|
export type ReceptionFormData = {
|
||||||
|
identificationNumber?: null|string,
|
||||||
licensePlate: string
|
licensePlate: string
|
||||||
receptionDate: string
|
receptionDate: string
|
||||||
receptionTypeId: string
|
receptionTypeId: string
|
||||||
@@ -79,6 +89,7 @@ export type ReceptionFormData = {
|
|||||||
carrierId: string
|
carrierId: string
|
||||||
driverId: string
|
driverId: string
|
||||||
vehicleId: string
|
vehicleId: string
|
||||||
|
weight?: ReceptionFormWeight | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ReceptionFormWeight = {
|
export type ReceptionFormWeight = {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export interface ShipmentTypeData {
|
|||||||
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
|
||||||
@@ -43,11 +43,11 @@ 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
|
||||||
|
|||||||
@@ -8,8 +8,11 @@ export default <Partial<Config>>{
|
|||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
primary: {
|
primary: {
|
||||||
|
700: '#35453C',
|
||||||
500: '#456452',
|
500: '#456452',
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
migrations/Version20260225110000.php
Normal file
26
migrations/Version20260225110000.php
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,7 +58,11 @@ class SeedCommand extends Command
|
|||||||
$this->seedMerchandiseTypes();
|
$this->seedMerchandiseTypes();
|
||||||
$this->seedPelletTypes();
|
$this->seedPelletTypes();
|
||||||
$this->seedBuildings();
|
$this->seedBuildings();
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
$this->seedBuildingInfrastructure();
|
$this->seedBuildingInfrastructure();
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
$this->seedBovines($io);
|
$this->seedBovines($io);
|
||||||
$this->seedReceptionTypes();
|
$this->seedReceptionTypes();
|
||||||
$this->seedBovineTypes();
|
$this->seedBovineTypes();
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ namespace App\Entity;
|
|||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
|
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
|
||||||
use App\Repository\BuildingCaseRepository;
|
|
||||||
use App\State\BuildingCaseWeightsReportProvider;
|
use App\State\BuildingCaseWeightsReportProvider;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
@@ -15,7 +14,7 @@ use Doctrine\ORM\Mapping as ORM;
|
|||||||
use Symfony\Component\Serializer\Attribute\Groups;
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
use Symfony\Component\Serializer\Attribute\SerializedName;
|
use Symfony\Component\Serializer\Attribute\SerializedName;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: BuildingCaseRepository::class)]
|
#[ORM\Entity]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(
|
new Get(
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use App\Repository\BuildingCasePositionRepository;
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Serializer\Attribute\Groups;
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
use Symfony\Component\Serializer\Attribute\SerializedName;
|
use Symfony\Component\Serializer\Attribute\SerializedName;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: BuildingCasePositionRepository::class)]
|
#[ORM\Entity]
|
||||||
class BuildingCasePosition
|
class BuildingCasePosition
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
|||||||
@@ -4,14 +4,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use App\Repository\BuildingLayoutRepository;
|
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
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;
|
||||||
use Symfony\Component\Serializer\Attribute\SerializedName;
|
use Symfony\Component\Serializer\Attribute\SerializedName;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: BuildingLayoutRepository::class)]
|
#[ORM\Entity]
|
||||||
class BuildingLayout
|
class BuildingLayout
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
|
|||||||
@@ -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'])]
|
||||||
@@ -162,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
|
||||||
|
|||||||
@@ -7,14 +7,13 @@ 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 App\Repository\StatutRepository;
|
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
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;
|
||||||
use Symfony\Component\Serializer\Attribute\SerializedName;
|
use Symfony\Component\Serializer\Attribute\SerializedName;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: StatutRepository::class)]
|
#[ORM\Entity]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(
|
new Get(
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Repository;
|
|
||||||
|
|
||||||
use App\Entity\BuildingCasePosition;
|
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends ServiceEntityRepository<BuildingCasePosition>
|
|
||||||
*/
|
|
||||||
class BuildingCasePositionRepository extends ServiceEntityRepository
|
|
||||||
{
|
|
||||||
public function __construct(ManagerRegistry $registry)
|
|
||||||
{
|
|
||||||
parent::__construct($registry, BuildingCasePosition::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * @return BuildingCasePosition[] Returns an array of BuildingCasePosition objects
|
|
||||||
// */
|
|
||||||
// public function findByExampleField($value): array
|
|
||||||
// {
|
|
||||||
// return $this->createQueryBuilder('b')
|
|
||||||
// ->andWhere('b.exampleField = :val')
|
|
||||||
// ->setParameter('val', $value)
|
|
||||||
// ->orderBy('b.id', 'ASC')
|
|
||||||
// ->setMaxResults(10)
|
|
||||||
// ->getQuery()
|
|
||||||
// ->getResult()
|
|
||||||
// ;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public function findOneBySomeField($value): ?BuildingCasePosition
|
|
||||||
// {
|
|
||||||
// return $this->createQueryBuilder('b')
|
|
||||||
// ->andWhere('b.exampleField = :val')
|
|
||||||
// ->setParameter('val', $value)
|
|
||||||
// ->getQuery()
|
|
||||||
// ->getOneOrNullResult()
|
|
||||||
// ;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Repository;
|
|
||||||
|
|
||||||
use App\Entity\BuildingCase;
|
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends ServiceEntityRepository<BuildingCase>
|
|
||||||
*/
|
|
||||||
class BuildingCaseRepository extends ServiceEntityRepository
|
|
||||||
{
|
|
||||||
public function __construct(ManagerRegistry $registry)
|
|
||||||
{
|
|
||||||
parent::__construct($registry, BuildingCase::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * @return BuildingCase[] Returns an array of BuildingCase objects
|
|
||||||
// */
|
|
||||||
// public function findByExampleField($value): array
|
|
||||||
// {
|
|
||||||
// return $this->createQueryBuilder('b')
|
|
||||||
// ->andWhere('b.exampleField = :val')
|
|
||||||
// ->setParameter('val', $value)
|
|
||||||
// ->orderBy('b.id', 'ASC')
|
|
||||||
// ->setMaxResults(10)
|
|
||||||
// ->getQuery()
|
|
||||||
// ->getResult()
|
|
||||||
// ;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public function findOneBySomeField($value): ?BuildingCase
|
|
||||||
// {
|
|
||||||
// return $this->createQueryBuilder('b')
|
|
||||||
// ->andWhere('b.exampleField = :val')
|
|
||||||
// ->setParameter('val', $value)
|
|
||||||
// ->getQuery()
|
|
||||||
// ->getOneOrNullResult()
|
|
||||||
// ;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Repository;
|
|
||||||
|
|
||||||
use App\Entity\BuildingLayout;
|
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends ServiceEntityRepository<BuildingLayout>
|
|
||||||
*/
|
|
||||||
class BuildingLayoutRepository extends ServiceEntityRepository
|
|
||||||
{
|
|
||||||
public function __construct(ManagerRegistry $registry)
|
|
||||||
{
|
|
||||||
parent::__construct($registry, BuildingLayout::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * @return BuildingLayout[] Returns an array of BuildingLayout objects
|
|
||||||
// */
|
|
||||||
// public function findByExampleField($value): array
|
|
||||||
// {
|
|
||||||
// return $this->createQueryBuilder('b')
|
|
||||||
// ->andWhere('b.exampleField = :val')
|
|
||||||
// ->setParameter('val', $value)
|
|
||||||
// ->orderBy('b.id', 'ASC')
|
|
||||||
// ->setMaxResults(10)
|
|
||||||
// ->getQuery()
|
|
||||||
// ->getResult()
|
|
||||||
// ;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public function findOneBySomeField($value): ?BuildingLayout
|
|
||||||
// {
|
|
||||||
// return $this->createQueryBuilder('b')
|
|
||||||
// ->andWhere('b.exampleField = :val')
|
|
||||||
// ->setParameter('val', $value)
|
|
||||||
// ->getQuery()
|
|
||||||
// ->getOneOrNullResult()
|
|
||||||
// ;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Repository;
|
|
||||||
|
|
||||||
use App\Entity\Statut;
|
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
|
||||||
use Doctrine\Persistence\ManagerRegistry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends ServiceEntityRepository<Statut>
|
|
||||||
*/
|
|
||||||
class StatutRepository extends ServiceEntityRepository
|
|
||||||
{
|
|
||||||
public function __construct(ManagerRegistry $registry)
|
|
||||||
{
|
|
||||||
parent::__construct($registry, Statut::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * @return Statut[] Returns an array of Statut objects
|
|
||||||
// */
|
|
||||||
// public function findByExampleField($value): array
|
|
||||||
// {
|
|
||||||
// return $this->createQueryBuilder('s')
|
|
||||||
// ->andWhere('s.exampleField = :val')
|
|
||||||
// ->setParameter('val', $value)
|
|
||||||
// ->orderBy('s.id', 'ASC')
|
|
||||||
// ->setMaxResults(10)
|
|
||||||
// ->getQuery()
|
|
||||||
// ->getResult()
|
|
||||||
// ;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public function findOneBySomeField($value): ?Statut
|
|
||||||
// {
|
|
||||||
// return $this->createQueryBuilder('s')
|
|
||||||
// ->andWhere('s.exampleField = :val')
|
|
||||||
// ->setParameter('val', $value)
|
|
||||||
// ->getQuery()
|
|
||||||
// ->getOneOrNullResult()
|
|
||||||
// ;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -150,11 +150,7 @@ final readonly class BuildingCaseWeightsReportProvider implements ProviderInterf
|
|||||||
|
|
||||||
private function resolveDailyGainKg(?string $breedCode): float
|
private function resolveDailyGainKg(?string $breedCode): float
|
||||||
{
|
{
|
||||||
return match ($breedCode) {
|
return 1.3;
|
||||||
'34' => 1.3, // Limousin
|
|
||||||
'38' => 1.5, // Charolais
|
|
||||||
default => 1.4, // Other breeds
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -178,39 +178,9 @@
|
|||||||
/* =========================
|
/* =========================
|
||||||
FOOTER VACCINS / NOTES
|
FOOTER VACCINS / NOTES
|
||||||
========================= */
|
========================= */
|
||||||
.footer {
|
.footer {
|
||||||
margin-top: 6px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer th {
|
|
||||||
background: #ffffff;
|
|
||||||
font-size: 9px;
|
|
||||||
font-weight: 700;
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer td {
|
|
||||||
font-size: 8px;
|
|
||||||
padding: 2px;
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer .small {
|
|
||||||
font-size: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ligne méta */
|
|
||||||
.meta {
|
|
||||||
margin-top: 3px;
|
|
||||||
text-align: right;
|
|
||||||
font-size: 8px;
|
|
||||||
color: #444;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Petits ajustements dompdf */
|
|
||||||
.mt-0 { margin-top: 0; }
|
|
||||||
.mb-0 { margin-bottom: 0; }
|
|
||||||
|
|
||||||
/* Header droit sans bordures par défaut */
|
/* Header droit sans bordures par défaut */
|
||||||
.header-right-free,
|
.header-right-free,
|
||||||
@@ -363,68 +333,46 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<!-- =========================
|
<!-- =========================
|
||||||
FOOTER (traitements / vaccins)
|
FOOTER (traitements / vaccins)
|
||||||
========================= -->
|
========================= -->
|
||||||
{# <table class="footer">#}
|
<table class="footer" style="border-collapse:collapse; margin-top: 32px">
|
||||||
{# <colgroup>#}
|
<tr>
|
||||||
{# #}{# 14 colonnes pour gérer proprement les colspan #}
|
<td style="height: 20px; border: 0" colspan="4"></td>
|
||||||
{# <col style="width:12.5%">#}
|
<td style="font-weight: 700" colspan="2">Grippe</td>
|
||||||
{# <col style="width:8%">#}
|
<td style="font-weight: 700" colspan="2">Protivity</td>
|
||||||
{# <col style="width:12.5%">#}
|
</tr>
|
||||||
{# <col style="width:8%">#}
|
<tr>
|
||||||
{# <col style="width:12.5%">#}
|
<td style="height: 20px">Date</td>
|
||||||
{# <col style="width:8%">#}
|
<td>Antibiotique</td>
|
||||||
{# <col style="width:12.5%">#}
|
<td>Date</td>
|
||||||
{# <col style="width:8%">#}
|
<td>Antero</td>
|
||||||
{# <col style="width:8%">#}
|
<td>Date</td>
|
||||||
{# <col style="width:6%">#}
|
<td>Intranasale</td>
|
||||||
{# <col style="width:8%">#}
|
<td>Date</td>
|
||||||
{# <col style="width:8%">#}
|
<td>Rappel 30 jours</td>
|
||||||
{# <col style="width:6%">#}
|
</tr>
|
||||||
{# <col style="width:8%">#}
|
<tr>
|
||||||
{# </colgroup>#}
|
<td style="height: 20px"></td>
|
||||||
|
<td></td>
|
||||||
{# <tr>#}
|
<td></td>
|
||||||
{# <th colspan="2">Date</th>#}
|
<td></td>
|
||||||
{# <th colspan="2">Antibiotique</th>#}
|
<td></td>
|
||||||
{# <th colspan="2">Date</th>#}
|
<td></td>
|
||||||
{# <th colspan="2">Antero</th>#}
|
<td></td>
|
||||||
{# <th colspan="3">Grippe</th>#}
|
<td></td>
|
||||||
{# <th colspan="3">Protivity</th>#}
|
</tr>
|
||||||
{# </tr>#}
|
<tr>
|
||||||
|
<td style="height: 20px"></td>
|
||||||
{# <tr>#}
|
<td></td>
|
||||||
{# <td colspan="2"></td>#}
|
<td></td>
|
||||||
{# <td colspan="2"></td>#}
|
<td></td>
|
||||||
{# <td colspan="2"></td>#}
|
<td></td>
|
||||||
{# <td colspan="2"></td>#}
|
<td></td>
|
||||||
|
<td></td>
|
||||||
{# <td class="small">Date</td>#}
|
<td></td>
|
||||||
{# <td colspan="2" class="small">Intranasale</td>#}
|
</tr>
|
||||||
|
</table>
|
||||||
{# <td class="small">Date</td>#}
|
|
||||||
{# <td colspan="2" class="small">Rappel 30 jours</td>#}
|
|
||||||
{# </tr>#}
|
|
||||||
|
|
||||||
{# <tr>#}
|
|
||||||
{# <td colspan="2"></td>#}
|
|
||||||
{# <td colspan="2"></td>#}
|
|
||||||
{# <td colspan="2"></td>#}
|
|
||||||
{# <td colspan="2" class="small">Rappel 30 jours</td>#}
|
|
||||||
|
|
||||||
{# <td></td>#}
|
|
||||||
{# <td colspan="2" class="small">Rappel 90 jours</td>#}
|
|
||||||
|
|
||||||
{# <td></td>#}
|
|
||||||
{# <td colspan="2" class="small">3 semaines</td>#}
|
|
||||||
{# </tr>#}
|
|
||||||
{# </table>#}
|
|
||||||
|
|
||||||
{# <div class="meta">#}
|
|
||||||
{# Édité le {{ printedAt|date('d/m/Y H:i') }} - Case {{ buildingCase.code ?? '' }}#}
|
|
||||||
{# </div>#}
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -220,9 +220,15 @@
|
|||||||
|
|
||||||
<tr class="border-bottom">
|
<tr class="border-bottom">
|
||||||
<td>
|
<td>
|
||||||
|
{% if reception.receptionType and reception.receptionType.code == 'BOVINS' %}
|
||||||
<strong>
|
<strong>
|
||||||
Type de bovins
|
Type de bovins
|
||||||
</strong>
|
</strong>
|
||||||
|
{% else %}
|
||||||
|
<strong>
|
||||||
|
Type de marchandises
|
||||||
|
</strong>
|
||||||
|
{% endif %}
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
<div class="bigtable-notes">
|
<div class="bigtable-notes">
|
||||||
@@ -263,6 +269,9 @@
|
|||||||
<p>Aucun dépôt de granulés renseigné.</p>
|
<p>Aucun dépôt de granulés renseigné.</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
{% if reception.merchandiseType %}
|
||||||
|
<p><strong>{{ reception.merchandiseType.label }}</strong></p>
|
||||||
|
{% endif %}
|
||||||
{% set buildingLabels = [] %}
|
{% set buildingLabels = [] %}
|
||||||
{% for building in reception.buildings|default([]) %}
|
{% for building in reception.buildings|default([]) %}
|
||||||
{% set buildingLabels = buildingLabels|merge([building.label]) %}
|
{% set buildingLabels = buildingLabels|merge([building.label]) %}
|
||||||
|
|||||||
@@ -275,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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user