feat : ajout de la partie reception des marchandises (étape 3) et modification du bon de réception
This commit is contained in:
25
.idea/workspace.xml
generated
25
.idea/workspace.xml
generated
@@ -4,15 +4,15 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : finalisation de l'étape 1 "Réception" (formulaire)">
|
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : ajout de la partie reception des marchandises (étape 3) et modification du bon de réception">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/AGENTS.md" beforeDir="false" afterPath="$PROJECT_DIR$/AGENTS.md" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/AGENTS.md" beforeDir="false" afterPath="$PROJECT_DIR$/AGENTS.md" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-weight.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-weight.vue" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-unloading.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-product-received.vue" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/components/ui/pdf-printer.vue" beforeDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/composables/usePdfPrinter.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/composables/usePdfPrinter.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/composables/usePdfPrinter.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/composables/usePdfPrinter.ts" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/frontend/i18n/locales/fr.json" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/i18n/locales/fr.json" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/frontend/pages/reception/[[id]].vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/reception/[[id]].vue" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/services/dto/reception-data.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/services/dto/reception-data.ts" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/frontend/services/dto/reception-data.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/services/dto/reception-data.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/Entity/Address.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Entity/Address.php" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/Entity/Reception.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Entity/Reception.php" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/Entity/Reception.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Entity/Reception.php" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/templates/reception_voucher.html.twig" beforeDir="false" afterPath="$PROJECT_DIR$/templates/reception_voucher.html.twig" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/templates/reception_voucher.html.twig" beforeDir="false" afterPath="$PROJECT_DIR$/templates/reception_voucher.html.twig" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
@@ -278,7 +278,8 @@
|
|||||||
<workItem from="1768894030675" duration="83922000" />
|
<workItem from="1768894030675" duration="83922000" />
|
||||||
<workItem from="1769413136483" duration="58000" />
|
<workItem from="1769413136483" duration="58000" />
|
||||||
<workItem from="1769413279223" duration="40490000" />
|
<workItem from="1769413279223" duration="40490000" />
|
||||||
<workItem from="1769612160652" duration="12834000" />
|
<workItem from="1769612160652" duration="23952000" />
|
||||||
|
<workItem from="1769696465294" duration="4048000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="feat : Ajout de pinia, création de la table weight et reception mise en place du système de step pour les receptions (WIP)">
|
<task id="LOCAL-00001" summary="feat : Ajout de pinia, création de la table weight et reception mise en place du système de step pour les receptions (WIP)">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -576,7 +577,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1769529522614</updated>
|
<updated>1769529522614</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="38" />
|
<task id="LOCAL-00038" summary="feat : ajout du numéro identification des receptions et ajustement du bon de reception">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1769676223697</created>
|
||||||
|
<option name="number" value="00038" />
|
||||||
|
<option name="presentableId" value="LOCAL-00038" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1769676223697</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="39" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -626,7 +635,6 @@
|
|||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="VcsManagerConfiguration">
|
<component name="VcsManagerConfiguration">
|
||||||
<MESSAGE value="feat : ajout de l'authentification avec lexik" />
|
|
||||||
<MESSAGE value="fix : correction de l'accès au swagger en mode dev qui n'était plus accessible" />
|
<MESSAGE value="fix : correction de l'accès au swagger en mode dev qui n'était plus accessible" />
|
||||||
<MESSAGE value="feat : ajout de la conf pour le déploiement en recette" />
|
<MESSAGE value="feat : ajout de la conf pour le déploiement en recette" />
|
||||||
<MESSAGE value="fix : fix de la conf pour le déploiement en recette" />
|
<MESSAGE value="fix : fix de la conf pour le déploiement en recette" />
|
||||||
@@ -651,7 +659,8 @@
|
|||||||
<MESSAGE value="fix : modification de la conf du bundle ednotif" />
|
<MESSAGE value="fix : modification de la conf du bundle ednotif" />
|
||||||
<MESSAGE value="feat : update du CHANGELOG.md" />
|
<MESSAGE value="feat : update du CHANGELOG.md" />
|
||||||
<MESSAGE value="feat : finalisation de l'étape 1 "Réception" (formulaire)" />
|
<MESSAGE value="feat : finalisation de l'étape 1 "Réception" (formulaire)" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="feat : finalisation de l'étape 1 "Réception" (formulaire)" />
|
<MESSAGE value="feat : ajout du numéro identification des receptions et ajustement du bon de reception" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="feat : ajout du numéro identification des receptions et ajustement du bon de reception" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ Backend conventions
|
|||||||
- API Platform operations are defined on Doctrine entities.
|
- API Platform operations are defined on Doctrine entities.
|
||||||
- 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`).
|
||||||
- `date_reception` is set by the UI, stored as `DateTimeImmutable`, serialized as `Y-m-d`.
|
- `date_reception` is set by the UI, stored as `DateTimeImmutable`, serialized as `Y-m-d`.
|
||||||
- Weight entity (`src/Entity/Weight.php`) is 1–N with Reception, each row stores `type` (`gross` or `tare`), `dsd`, `weight`, `weighed_at` (all nullable except `type`).
|
- Weight entity (`src/Entity/Weight.php`) is 1–N with Reception, each row stores `type` (`gross` or `tare`), `dsd`, `weight`, `weighed_at` (all nullable except `type`).
|
||||||
- Weigh endpoint `/receptions/weigh` returns `PontBasculeReading` with `dsd`, `weight`, `weighedAt` (formatted `Y-m-d`).
|
- Weigh endpoint `/receptions/weigh` returns `PontBasculeReading` with `dsd`, `weight`, `weighedAt` (formatted `Y-m-d`).
|
||||||
@@ -29,6 +30,7 @@ Frontend conventions
|
|||||||
- Zod is used for form validation (e.g. `frontend/components/reception/reception-form.vue`); shared helpers live in `frontend/utils/zod-errors.ts`.
|
- Zod is used for form validation (e.g. `frontend/components/reception/reception-form.vue`); shared helpers live in `frontend/utils/zod-errors.ts`.
|
||||||
- Weighing logic is shared via `frontend/composables/useWeighing.ts`.
|
- Weighing logic is shared via `frontend/composables/useWeighing.ts`.
|
||||||
- Reception step UI uses store state (`currentStep`) in `frontend/pages/reception/[[id]].vue`.
|
- Reception step UI uses store state (`currentStep`) in `frontend/pages/reception/[[id]].vue`.
|
||||||
|
- Step 2 uses `frontend/components/reception/reception-product-received.vue` for merchandise selection; type codes in `frontend/utils/constants.ts`.
|
||||||
- Active nav styles in header use `NuxtLink` with `custom` slot.
|
- Active nav styles in header use `NuxtLink` with `custom` slot.
|
||||||
- Reusable UI components live under `frontend/components/ui/` and are auto-imported with `Ui` prefix (e.g. `UiLoadingDots`).
|
- Reusable UI components live under `frontend/components/ui/` and are auto-imported with `Ui` prefix (e.g. `UiLoadingDots`).
|
||||||
- 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/`.
|
||||||
@@ -47,6 +49,8 @@ Notes
|
|||||||
- Keep endpoints in plural (API Platform convention).
|
- Keep endpoints in plural (API Platform convention).
|
||||||
- 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`).
|
||||||
|
- Buildings (`building`, fields: `label`, `code`) and reception allocations (`reception_building` M2M, `reception_pellet_building` unique on reception/pellet/building).
|
||||||
- Suppliers (`supplier`) with addresses (`address`, fields: `label`, `street`, `postal_code`, `city`, `country_code` ISO2), via `supplier_address` join table.
|
- Suppliers (`supplier`) with addresses (`address`, fields: `label`, `street`, `postal_code`, `city`, `country_code` ISO2), via `supplier_address` join table.
|
||||||
- Trucks (`truck`, field: `name`), linked to receptions.
|
- Trucks (`truck`, field: `name`), linked to receptions.
|
||||||
- Carriers (`carrier`, fields: `name`, nullable `code`), Drivers (`driver`, fields: `name`, `carrier_id`), Vehicles (`vehicle`, fields: `plate`, `carrier_id`, `truck_id`) used for LIOT logic.
|
- Carriers (`carrier`, fields: `name`, nullable `code`), Drivers (`driver`, fields: `name`, `carrier_id`), Vehicles (`vehicle`, fields: `plate`, `carrier_id`, `truck_id`) used for LIOT logic.
|
||||||
|
|||||||
232
frontend/components/reception/reception-product-received.vue
Normal file
232
frontend/components/reception/reception-product-received.vue
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col items-center gap-16">
|
||||||
|
<!-- @TODO voir pour séparer dans un composant au moment de l'implémentation des Bovins -->
|
||||||
|
<div
|
||||||
|
v-if="receptionStore.current?.receptionType?.code === RECEPTION_TYPE_CODES.MERCHANDISES"
|
||||||
|
class="flex flex-col gap-8 items-center w-full">
|
||||||
|
<h1 class="text-4xl uppercase font-bold">Sélectionner des marchandises réceptionnnées</h1>
|
||||||
|
<div class="flex flex-col w-[550px]">
|
||||||
|
<label for="merchandise-type" class="font-bold uppercase text-xl mb-2">Type de marchandises</label>
|
||||||
|
<select
|
||||||
|
id="merchandise-type"
|
||||||
|
v-model="selectedMerchandiseTypeId"
|
||||||
|
class="border-b border-black justify-self-start text-xl pb-[6px] bg-transparent cursor-pointer"
|
||||||
|
:class="selectedMerchandiseTypeId ? 'text-black' : 'text-neutral-400'"
|
||||||
|
>
|
||||||
|
<option value="" disabled class="text-neutral-400">Sélectionner</option>
|
||||||
|
<option
|
||||||
|
v-for="type in merchandiseTypes"
|
||||||
|
:key="type.id"
|
||||||
|
:value="String(type.id)"
|
||||||
|
class="text-black"
|
||||||
|
>
|
||||||
|
{{ type.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="selectedMerchandiseTypeId && isAutres"
|
||||||
|
class="flex flex-col w-full max-w-[550px]"
|
||||||
|
>
|
||||||
|
<label for="merchandise-detail" class="font-bold uppercase text-xl mb-2">Préciser</label>
|
||||||
|
<input
|
||||||
|
id="merchandise-detail"
|
||||||
|
v-model="merchandiseDetail"
|
||||||
|
placeholder="Précisions complémentaires"
|
||||||
|
type="text"
|
||||||
|
maxlength="255"
|
||||||
|
class="border-b border-black text-xl pb-[6px] bg-transparent"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="selectedMerchandiseTypeId && !isGranule"
|
||||||
|
class="flex gap-4 w-[550px] justify-evenly"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="building in buildings"
|
||||||
|
:key="building.id"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="flex items-center gap-2 text-xl"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="selectedBuildingIds"
|
||||||
|
type="checkbox"
|
||||||
|
:value="String(building.id)"
|
||||||
|
>
|
||||||
|
<span>{{ building.label }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="selectedMerchandiseTypeId && isGranule"
|
||||||
|
class="flex flex-col gap-10 w-full max-w-[900px]"
|
||||||
|
>
|
||||||
|
<div class="grid grid-cols-1 gap-10 md:grid-cols-4">
|
||||||
|
<div v-for="type in pelletTypes" :key="type.id" class="flex flex-col gap-4">
|
||||||
|
<p class="font-bold uppercase">{{ type.label }}</p>
|
||||||
|
<label
|
||||||
|
v-for="building in buildings"
|
||||||
|
:key="building.id"
|
||||||
|
class="flex items-center gap-2 text-lg"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="selectedPelletBuildingIds[String(type.id)]"
|
||||||
|
type="checkbox"
|
||||||
|
:value="String(building.id)"
|
||||||
|
>
|
||||||
|
<span>{{ building.label }}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
||||||
|
@click="goNext"
|
||||||
|
>Peser</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
import { getBuildingList } from '~/services/building'
|
||||||
|
import { getMerchandiseTypeList } from '~/services/merchandise-type'
|
||||||
|
import type { MerchandiseTypeData } from '~/services/dto/merchandise-type-data'
|
||||||
|
import type { BuildingData } from '~/services/dto/building-data'
|
||||||
|
import type { PelletTypeData } from '~/services/dto/pellet-type-data'
|
||||||
|
import { getPelletTypeList } from '~/services/pellet-type'
|
||||||
|
import {
|
||||||
|
createReceptionPelletBuilding,
|
||||||
|
deleteReceptionPelletBuilding,
|
||||||
|
getReceptionPelletBuildingList
|
||||||
|
} from '~/services/reception-pellet-building'
|
||||||
|
import { useReceptionStore } from '~/stores/reception'
|
||||||
|
import { MERCHANDISE_TYPE_CODES, RECEPTION_TYPE_CODES } from '~/utils/constants'
|
||||||
|
|
||||||
|
const receptionStore = useReceptionStore()
|
||||||
|
const merchandiseTypes = ref<MerchandiseTypeData[]>([])
|
||||||
|
const buildings = ref<BuildingData[]>([])
|
||||||
|
const pelletTypes = ref<PelletTypeData[]>([])
|
||||||
|
const selectedMerchandiseTypeId = ref('')
|
||||||
|
const selectedBuildingIds = ref<string[]>([])
|
||||||
|
const selectedPelletBuildingIds = ref<Record<string, string[]>>({})
|
||||||
|
const merchandiseDetail = ref('')
|
||||||
|
|
||||||
|
const selectedMerchandiseType = computed(() =>
|
||||||
|
merchandiseTypes.value.find((type) => String(type.id) === selectedMerchandiseTypeId.value)
|
||||||
|
)
|
||||||
|
const isGranule = computed(() => selectedMerchandiseType.value?.code === MERCHANDISE_TYPE_CODES.GRANULE)
|
||||||
|
const isAutres = computed(() => selectedMerchandiseType.value?.code === MERCHANDISE_TYPE_CODES.AUTRES)
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const [merchandiseTypeList, buildingList, pelletTypeList] = await Promise.all([
|
||||||
|
getMerchandiseTypeList(),
|
||||||
|
getBuildingList(),
|
||||||
|
getPelletTypeList()
|
||||||
|
])
|
||||||
|
merchandiseTypes.value = merchandiseTypeList
|
||||||
|
buildings.value = buildingList
|
||||||
|
pelletTypes.value = pelletTypeList
|
||||||
|
|
||||||
|
const currentId = receptionStore.current?.merchandiseType?.id
|
||||||
|
if (currentId) {
|
||||||
|
selectedMerchandiseTypeId.value = String(currentId)
|
||||||
|
}
|
||||||
|
merchandiseDetail.value = receptionStore.current?.merchandiseDetail ?? ''
|
||||||
|
|
||||||
|
selectedBuildingIds.value =
|
||||||
|
receptionStore.current?.buildings?.map((building) => String(building.id)) ?? []
|
||||||
|
|
||||||
|
const existingPelletSelections = receptionStore.current?.pelletBuildings ?? []
|
||||||
|
const selectionMap: Record<string, string[]> = {}
|
||||||
|
for (const selection of existingPelletSelections) {
|
||||||
|
const pelletTypeId = String(selection.pelletType.id)
|
||||||
|
const buildingId = String(selection.building.id)
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
async function goNext() {
|
||||||
|
if (!receptionStore.current) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextStep = receptionStore.current.currentStep + 1
|
||||||
|
const receptionIri = `/api/receptions/${receptionStore.current.id}`
|
||||||
|
|
||||||
|
await receptionStore.updateReception(receptionStore.current.id, {
|
||||||
|
merchandiseType: selectedMerchandiseTypeId.value
|
||||||
|
? `/api/merchandise_types/${selectedMerchandiseTypeId.value}`
|
||||||
|
: null,
|
||||||
|
merchandiseDetail: isAutres.value ? merchandiseDetail.value.trim() : null,
|
||||||
|
buildings: isGranule.value
|
||||||
|
? []
|
||||||
|
: selectedBuildingIds.value.map((id) => `/api/buildings/${id}`),
|
||||||
|
currentStep: nextStep
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isGranule.value) {
|
||||||
|
await syncPelletSelections(receptionIri)
|
||||||
|
} else {
|
||||||
|
await clearPelletSelections(receptionIri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function clearPelletSelections(receptionIri: string) {
|
||||||
|
const existing = await getReceptionPelletBuildingList(receptionIri)
|
||||||
|
for (const selection of existing) {
|
||||||
|
await deleteReceptionPelletBuilding(selection.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncPelletSelections(receptionIri: string) {
|
||||||
|
const existing = await getReceptionPelletBuildingList(receptionIri)
|
||||||
|
const existingMap = new Map<string, number>()
|
||||||
|
for (const selection of existing) {
|
||||||
|
const key = `${selection.pelletType.id}:${selection.building.id}`
|
||||||
|
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>
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col items-center mt-[164px] gap-32">
|
|
||||||
<div class="flex gap-8 items-center justify-center">
|
|
||||||
<!--@TODO Prendre en compte que l'on peut aussi décharger de la marchandise-->
|
|
||||||
<h1 class="text-4xl uppercase font-bold">Décharger les bêtes</h1>
|
|
||||||
<UiLoadingDots />
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
@click="goNext"
|
|
||||||
>Peser</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useReceptionStore } from '~/stores/reception'
|
|
||||||
|
|
||||||
const receptionStore = useReceptionStore()
|
|
||||||
|
|
||||||
async function goNext() {
|
|
||||||
if (!receptionStore.current) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextStep = receptionStore.current.currentStep + 1
|
|
||||||
await receptionStore.updateReception(receptionStore.current.id, {
|
|
||||||
currentStep: nextStep
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -6,21 +6,26 @@ export const usePdfPrinter = () => {
|
|||||||
const currentReception = receptionStore.current
|
const currentReception = receptionStore.current
|
||||||
|
|
||||||
const printPdf = async (url: string): Promise<void> => {
|
const printPdf = async (url: string): Promise<void> => {
|
||||||
if (!import.meta.client) {
|
const blob = await api.getBlob(url);
|
||||||
return
|
|
||||||
}
|
const pdfBlob = blob.type === 'application/pdf'
|
||||||
|
? blob
|
||||||
|
: new Blob([blob], { type: 'application/pdf' });
|
||||||
|
|
||||||
|
const blobUrl = URL.createObjectURL(pdfBlob);
|
||||||
|
|
||||||
|
const filename = `${currentReception.identificationNumber}_${currentReception.supplier.name}_${currentReception.licensePlate}.pdf`;
|
||||||
|
|
||||||
const blob = await api.getBlob(url)
|
|
||||||
const blobUrl = URL.createObjectURL(blob)
|
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = blobUrl;
|
||||||
// nom du fichier à changer par les infos du store pinia
|
a.download = filename;
|
||||||
a.download = `${currentReception.identificationNumber}_${currentReception.supplier.name}_${currentReception.licensePlate}`;
|
a.style.display = 'none';
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a);
|
||||||
a.click();
|
a.click();
|
||||||
a.remove();
|
a.remove();
|
||||||
window.open(blobUrl, '_blank', 'noopener,noreferrer')
|
|
||||||
setTimeout(() => URL.revokeObjectURL(blobUrl), 60000)
|
window.open(blobUrl, '_blank', 'noopener,noreferrer');
|
||||||
|
setTimeout(() => URL.revokeObjectURL(blobUrl), 60_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -17,6 +17,20 @@
|
|||||||
"receptionType": {
|
"receptionType": {
|
||||||
"list": "Impossible de récupérer la liste des types de réception."
|
"list": "Impossible de récupérer la liste des types de réception."
|
||||||
},
|
},
|
||||||
|
"merchandiseType": {
|
||||||
|
"list": "Impossible de récupérer la liste des types de marchandises."
|
||||||
|
},
|
||||||
|
"building": {
|
||||||
|
"list": "Impossible de récupérer la liste des bâtiments."
|
||||||
|
},
|
||||||
|
"pelletType": {
|
||||||
|
"list": "Impossible de récupérer la liste des types de granulés."
|
||||||
|
},
|
||||||
|
"receptionPelletBuilding": {
|
||||||
|
"list": "Impossible de récupérer la liste des dépôts de granulés.",
|
||||||
|
"create": "Impossible d'enregistrer le dépôt de granulés.",
|
||||||
|
"delete": "Impossible de supprimer le dépôt de granulés."
|
||||||
|
},
|
||||||
"supplier": {
|
"supplier": {
|
||||||
"list": "Impossible de récupérer la liste des fournisseurs."
|
"list": "Impossible de récupérer la liste des fournisseurs."
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ReceptionForm v-if="!storeReception || storeReception.currentStep === 0"/>
|
<ReceptionForm v-if="!storeReception || storeReception.currentStep === 0"/>
|
||||||
<ReceptionWeight v-if="storeReception?.currentStep === 1" mode="gross"/>
|
<ReceptionWeight v-if="storeReception?.currentStep === 1" mode="gross"/>
|
||||||
<ReceptionUnloading v-if="storeReception?.currentStep === 2"/>
|
<ReceptionProductReceived v-if="storeReception?.currentStep === 2"/>
|
||||||
<ReceptionWeight v-if="storeReception?.currentStep !== null && storeReception?.currentStep >= 3" mode="tare"/>
|
<ReceptionWeight v-if="storeReception?.currentStep !== null && storeReception?.currentStep >= 3" mode="tare"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
23
frontend/services/building.ts
Normal file
23
frontend/services/building.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { useApi } from '~/composables/useApi'
|
||||||
|
import type { BuildingData } from '~/services/dto/building-data'
|
||||||
|
|
||||||
|
export type BuildingListResponse =
|
||||||
|
| BuildingData[]
|
||||||
|
| { 'hydra:member'?: BuildingData[] }
|
||||||
|
|
||||||
|
export async function getBuildingList(): Promise<BuildingData[]> {
|
||||||
|
const api = useApi()
|
||||||
|
const response = await api.get<BuildingListResponse>('buildings', {}, {
|
||||||
|
toastErrorKey: 'errors.building.list'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Array.isArray(response)) {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) {
|
||||||
|
return response['hydra:member']
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
5
frontend/services/dto/building-data.ts
Normal file
5
frontend/services/dto/building-data.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface BuildingData {
|
||||||
|
id: number
|
||||||
|
label: string
|
||||||
|
code: string
|
||||||
|
}
|
||||||
5
frontend/services/dto/merchandise-type-data.ts
Normal file
5
frontend/services/dto/merchandise-type-data.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface MerchandiseTypeData {
|
||||||
|
id: number
|
||||||
|
label: string
|
||||||
|
code: string
|
||||||
|
}
|
||||||
5
frontend/services/dto/pellet-type-data.ts
Normal file
5
frontend/services/dto/pellet-type-data.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export interface PelletTypeData {
|
||||||
|
id: number
|
||||||
|
label: string
|
||||||
|
code: string
|
||||||
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
import type { ReceptionTypeData } from '~/services/dto/reception-type-data'
|
import type { ReceptionTypeData } from '~/services/dto/reception-type-data'
|
||||||
|
import type { MerchandiseTypeData } from '~/services/dto/merchandise-type-data'
|
||||||
|
import type { BuildingData } from '~/services/dto/building-data'
|
||||||
|
import type { ReceptionPelletBuildingData } from '~/services/dto/reception-pellet-building-data'
|
||||||
import type { UserData } from '~/services/dto/user-data'
|
import type { UserData } from '~/services/dto/user-data'
|
||||||
import type { SupplierData } from '~/services/dto/supplier-data'
|
import type { SupplierData } from '~/services/dto/supplier-data'
|
||||||
import type { AddressData } from '~/services/dto/address-data'
|
import type { AddressData } from '~/services/dto/address-data'
|
||||||
@@ -15,6 +18,10 @@ export interface ReceptionData {
|
|||||||
currentStep: number
|
currentStep: number
|
||||||
isValid: boolean
|
isValid: boolean
|
||||||
receptionType?: ReceptionTypeData | null
|
receptionType?: ReceptionTypeData | null
|
||||||
|
merchandiseType?: MerchandiseTypeData | null
|
||||||
|
merchandiseDetail?: string | null
|
||||||
|
buildings?: BuildingData[] | null
|
||||||
|
pelletBuildings?: ReceptionPelletBuildingData[] | null
|
||||||
user?: UserData | null
|
user?: UserData | null
|
||||||
supplier?: SupplierData | null
|
supplier?: SupplierData | null
|
||||||
address?: AddressData | null
|
address?: AddressData | null
|
||||||
@@ -37,6 +44,9 @@ export type ReceptionPayload = {
|
|||||||
currentStep?: number
|
currentStep?: number
|
||||||
isValid?: boolean
|
isValid?: boolean
|
||||||
receptionType?: string | null
|
receptionType?: string | null
|
||||||
|
merchandiseType?: string | null
|
||||||
|
merchandiseDetail?: string | null
|
||||||
|
buildings?: string[] | null
|
||||||
user?: string | null
|
user?: string | null
|
||||||
supplier?: string | null
|
supplier?: string | null
|
||||||
address?: string | null
|
address?: string | null
|
||||||
|
|||||||
9
frontend/services/dto/reception-pellet-building-data.ts
Normal file
9
frontend/services/dto/reception-pellet-building-data.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { BuildingData } from '~/services/dto/building-data'
|
||||||
|
import type { PelletTypeData } from '~/services/dto/pellet-type-data'
|
||||||
|
|
||||||
|
export interface ReceptionPelletBuildingData {
|
||||||
|
id: number
|
||||||
|
reception?: string
|
||||||
|
building: BuildingData
|
||||||
|
pelletType: PelletTypeData
|
||||||
|
}
|
||||||
23
frontend/services/merchandise-type.ts
Normal file
23
frontend/services/merchandise-type.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { useApi } from '~/composables/useApi'
|
||||||
|
import type { MerchandiseTypeData } from '~/services/dto/merchandise-type-data'
|
||||||
|
|
||||||
|
export type MerchandiseTypeListResponse =
|
||||||
|
| MerchandiseTypeData[]
|
||||||
|
| { 'hydra:member'?: MerchandiseTypeData[] }
|
||||||
|
|
||||||
|
export async function getMerchandiseTypeList(): Promise<MerchandiseTypeData[]> {
|
||||||
|
const api = useApi()
|
||||||
|
const response = await api.get<MerchandiseTypeListResponse>('merchandise_types', {}, {
|
||||||
|
toastErrorKey: 'errors.merchandiseType.list'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Array.isArray(response)) {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) {
|
||||||
|
return response['hydra:member']
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
23
frontend/services/pellet-type.ts
Normal file
23
frontend/services/pellet-type.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { useApi } from '~/composables/useApi'
|
||||||
|
import type { PelletTypeData } from '~/services/dto/pellet-type-data'
|
||||||
|
|
||||||
|
export type PelletTypeListResponse =
|
||||||
|
| PelletTypeData[]
|
||||||
|
| { 'hydra:member'?: PelletTypeData[] }
|
||||||
|
|
||||||
|
export async function getPelletTypeList(): Promise<PelletTypeData[]> {
|
||||||
|
const api = useApi()
|
||||||
|
const response = await api.get<PelletTypeListResponse>('pellet_types', {}, {
|
||||||
|
toastErrorKey: 'errors.pelletType.list'
|
||||||
|
})
|
||||||
|
|
||||||
|
if (Array.isArray(response)) {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) {
|
||||||
|
return response['hydra:member']
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
51
frontend/services/reception-pellet-building.ts
Normal file
51
frontend/services/reception-pellet-building.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { useApi } from '~/composables/useApi'
|
||||||
|
import type { ReceptionPelletBuildingData } from '~/services/dto/reception-pellet-building-data'
|
||||||
|
|
||||||
|
export type ReceptionPelletBuildingListResponse =
|
||||||
|
| ReceptionPelletBuildingData[]
|
||||||
|
| { 'hydra:member'?: ReceptionPelletBuildingData[] }
|
||||||
|
|
||||||
|
export type ReceptionPelletBuildingPayload = {
|
||||||
|
reception: string
|
||||||
|
pelletType: string
|
||||||
|
building: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getReceptionPelletBuildingList(
|
||||||
|
receptionIri: string
|
||||||
|
): Promise<ReceptionPelletBuildingData[]> {
|
||||||
|
const api = useApi()
|
||||||
|
const response = await api.get<ReceptionPelletBuildingListResponse>(
|
||||||
|
'reception_pellet_buildings',
|
||||||
|
{ reception: receptionIri },
|
||||||
|
{
|
||||||
|
toastErrorKey: 'errors.receptionPelletBuilding.list'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (Array.isArray(response)) {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) {
|
||||||
|
return response['hydra:member']
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createReceptionPelletBuilding(
|
||||||
|
payload: ReceptionPelletBuildingPayload
|
||||||
|
): Promise<ReceptionPelletBuildingData> {
|
||||||
|
const api = useApi()
|
||||||
|
return api.post<ReceptionPelletBuildingData>('reception_pellet_buildings', payload, {
|
||||||
|
toastErrorKey: 'errors.receptionPelletBuilding.create'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteReceptionPelletBuilding(id: number): Promise<void> {
|
||||||
|
const api = useApi()
|
||||||
|
await api.delete<void>(`reception_pellet_buildings/${id}`, {}, {
|
||||||
|
toastErrorKey: 'errors.receptionPelletBuilding.delete'
|
||||||
|
})
|
||||||
|
}
|
||||||
8
frontend/utils/constants.ts
Normal file
8
frontend/utils/constants.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export const RECEPTION_TYPE_CODES = {
|
||||||
|
MERCHANDISES: 'MARCHANDISES'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const MERCHANDISE_TYPE_CODES = {
|
||||||
|
GRANULE: 'GRANULE',
|
||||||
|
AUTRES: 'AUTRES'
|
||||||
|
} as const
|
||||||
32
migrations/Version20260128000200.php
Normal file
32
migrations/Version20260128000200.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20260128000200 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add merchandise types and link receptions';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('CREATE TABLE merchandise_type (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, label VARCHAR(120) NOT NULL, code VARCHAR(50) NOT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('ALTER TABLE reception ADD merchandise_type_id INT DEFAULT NULL');
|
||||||
|
$this->addSql('CREATE INDEX IDX_83DC02E3BCAAA7C0 ON reception (merchandise_type_id)');
|
||||||
|
$this->addSql('ALTER TABLE reception ADD CONSTRAINT FK_83DC02E3BCAAA7C0 FOREIGN KEY (merchandise_type_id) REFERENCES merchandise_type (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE reception DROP CONSTRAINT FK_83DC02E3BCAAA7C0');
|
||||||
|
$this->addSql('DROP INDEX IDX_83DC02E3BCAAA7C0');
|
||||||
|
$this->addSql('ALTER TABLE reception DROP merchandise_type_id');
|
||||||
|
$this->addSql('DROP TABLE merchandise_type');
|
||||||
|
}
|
||||||
|
}
|
||||||
48
migrations/Version20260128000300.php
Normal file
48
migrations/Version20260128000300.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
final class Version20260128000300 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add buildings, pellet types, and reception allocations';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('CREATE TABLE building (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, label VARCHAR(120) NOT NULL, code VARCHAR(50) NOT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('CREATE TABLE pellet_type (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, label VARCHAR(120) NOT NULL, code VARCHAR(50) NOT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('CREATE TABLE reception_building (reception_id INT NOT NULL, building_id INT NOT NULL, PRIMARY KEY(reception_id, building_id))');
|
||||||
|
$this->addSql('CREATE INDEX IDX_46E7F9F23E4A2E34 ON reception_building (reception_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_46E7F9F24D2A7E12 ON reception_building (building_id)');
|
||||||
|
$this->addSql('ALTER TABLE reception_building ADD CONSTRAINT FK_46E7F9F23E4A2E34 FOREIGN KEY (reception_id) REFERENCES reception (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE reception_building ADD CONSTRAINT FK_46E7F9F24D2A7E12 FOREIGN KEY (building_id) REFERENCES building (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('CREATE TABLE reception_pellet_building (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, reception_id INT NOT NULL, pellet_type_id INT NOT NULL, building_id INT NOT NULL, PRIMARY KEY(id))');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX uniq_reception_pellet_building ON reception_pellet_building (reception_id, pellet_type_id, building_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_5DF3AA933E4A2E34 ON reception_pellet_building (reception_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_5DF3AA93955258D ON reception_pellet_building (pellet_type_id)');
|
||||||
|
$this->addSql('CREATE INDEX IDX_5DF3AA934D2A7E12 ON reception_pellet_building (building_id)');
|
||||||
|
$this->addSql('ALTER TABLE reception_pellet_building ADD CONSTRAINT FK_5DF3AA933E4A2E34 FOREIGN KEY (reception_id) REFERENCES reception (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE reception_pellet_building ADD CONSTRAINT FK_5DF3AA93955258D FOREIGN KEY (pellet_type_id) REFERENCES pellet_type (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
$this->addSql('ALTER TABLE reception_pellet_building ADD CONSTRAINT FK_5DF3AA934D2A7E12 FOREIGN KEY (building_id) REFERENCES building (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE reception_pellet_building DROP CONSTRAINT FK_5DF3AA933E4A2E34');
|
||||||
|
$this->addSql('ALTER TABLE reception_pellet_building DROP CONSTRAINT FK_5DF3AA93955258D');
|
||||||
|
$this->addSql('ALTER TABLE reception_pellet_building DROP CONSTRAINT FK_5DF3AA934D2A7E12');
|
||||||
|
$this->addSql('DROP TABLE reception_pellet_building');
|
||||||
|
$this->addSql('ALTER TABLE reception_building DROP CONSTRAINT FK_46E7F9F23E4A2E34');
|
||||||
|
$this->addSql('ALTER TABLE reception_building DROP CONSTRAINT FK_46E7F9F24D2A7E12');
|
||||||
|
$this->addSql('DROP TABLE reception_building');
|
||||||
|
$this->addSql('DROP TABLE pellet_type');
|
||||||
|
$this->addSql('DROP TABLE building');
|
||||||
|
}
|
||||||
|
}
|
||||||
26
migrations/Version20260128000400.php
Normal file
26
migrations/Version20260128000400.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 Version20260128000400 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'Add merchandise detail to reception';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE reception ADD merchandise_detail VARCHAR(255) DEFAULT NULL');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql('ALTER TABLE reception DROP merchandise_detail');
|
||||||
|
}
|
||||||
|
}
|
||||||
92
src/Entity/Building.php
Normal file
92
src/Entity/Building.php
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
|
#[ORM\Entity]
|
||||||
|
#[ORM\Table(name: 'building')]
|
||||||
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(
|
||||||
|
requirements: ['id' => '\d+'],
|
||||||
|
normalizationContext: ['groups' => ['building:read']],
|
||||||
|
),
|
||||||
|
new GetCollection(
|
||||||
|
normalizationContext: ['groups' => ['building:read']],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
security: "is_granted('ROLE_USER')",
|
||||||
|
)]
|
||||||
|
class Building
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
#[Groups(['building:read', 'reception:read'])]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 120)]
|
||||||
|
#[Groups(['building:read', 'reception:read'])]
|
||||||
|
private string $label = '';
|
||||||
|
|
||||||
|
#[ORM\Column(length: 50)]
|
||||||
|
#[Groups(['building:read', 'reception:read'])]
|
||||||
|
private string $code = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<int, Reception>
|
||||||
|
*/
|
||||||
|
#[ORM\ManyToMany(targetEntity: Reception::class, mappedBy: 'buildings')]
|
||||||
|
private Collection $receptions;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->receptions = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabel(): string
|
||||||
|
{
|
||||||
|
return $this->label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLabel(string $label): self
|
||||||
|
{
|
||||||
|
$this->label = $label;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCode(): string
|
||||||
|
{
|
||||||
|
return $this->code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCode(string $code): self
|
||||||
|
{
|
||||||
|
$this->code = $code;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, Reception>
|
||||||
|
*/
|
||||||
|
public function getReceptions(): Collection
|
||||||
|
{
|
||||||
|
return $this->receptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
92
src/Entity/MerchandiseType.php
Normal file
92
src/Entity/MerchandiseType.php
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
|
#[ORM\Entity]
|
||||||
|
#[ORM\Table(name: 'merchandise_type')]
|
||||||
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(
|
||||||
|
requirements: ['id' => '\d+'],
|
||||||
|
normalizationContext: ['groups' => ['merchandise-type:read']],
|
||||||
|
),
|
||||||
|
new GetCollection(
|
||||||
|
normalizationContext: ['groups' => ['merchandise-type:read']],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
security: "is_granted('ROLE_USER')",
|
||||||
|
)]
|
||||||
|
class MerchandiseType
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
#[Groups(['reception:read', 'merchandise-type:read'])]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 120)]
|
||||||
|
#[Groups(['reception:read', 'merchandise-type:read'])]
|
||||||
|
private string $label = '';
|
||||||
|
|
||||||
|
#[ORM\Column(length: 50)]
|
||||||
|
#[Groups(['reception:read', 'merchandise-type:read'])]
|
||||||
|
private string $code = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<int, Reception>
|
||||||
|
*/
|
||||||
|
#[ORM\OneToMany(mappedBy: 'merchandiseType', targetEntity: Reception::class)]
|
||||||
|
private Collection $receptions;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->receptions = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabel(): string
|
||||||
|
{
|
||||||
|
return $this->label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLabel(string $label): self
|
||||||
|
{
|
||||||
|
$this->label = $label;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCode(): string
|
||||||
|
{
|
||||||
|
return $this->code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCode(string $code): self
|
||||||
|
{
|
||||||
|
$this->code = $code;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, Reception>
|
||||||
|
*/
|
||||||
|
public function getReceptions(): Collection
|
||||||
|
{
|
||||||
|
return $this->receptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/Entity/PelletType.php
Normal file
71
src/Entity/PelletType.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
|
#[ORM\Entity]
|
||||||
|
#[ORM\Table(name: 'pellet_type')]
|
||||||
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(
|
||||||
|
requirements: ['id' => '\d+'],
|
||||||
|
normalizationContext: ['groups' => ['pellet-type:read']],
|
||||||
|
),
|
||||||
|
new GetCollection(
|
||||||
|
normalizationContext: ['groups' => ['pellet-type:read']],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
security: "is_granted('ROLE_USER')",
|
||||||
|
)]
|
||||||
|
class PelletType
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
#[Groups(['pellet-type:read', 'reception:read'])]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 120)]
|
||||||
|
#[Groups(['pellet-type:read', 'reception:read'])]
|
||||||
|
private string $label = '';
|
||||||
|
|
||||||
|
#[ORM\Column(length: 50)]
|
||||||
|
#[Groups(['pellet-type:read', 'reception:read'])]
|
||||||
|
private string $code = '';
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabel(): string
|
||||||
|
{
|
||||||
|
return $this->label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLabel(string $label): self
|
||||||
|
{
|
||||||
|
$this->label = $label;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCode(): string
|
||||||
|
{
|
||||||
|
return $this->code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCode(string $code): self
|
||||||
|
{
|
||||||
|
$this->code = $code;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -96,6 +96,10 @@ class Reception
|
|||||||
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
#[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
|
||||||
private ?DateTimeImmutable $receptionDate = null;
|
private ?DateTimeImmutable $receptionDate = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
#[Groups(['reception:read', 'reception:write'])]
|
||||||
|
private ?string $merchandiseDetail = null;
|
||||||
|
|
||||||
#[ORM\OneToMany(targetEntity: Weight::class, mappedBy: 'reception', cascade: ['persist', 'remove'], orphanRemoval: true)]
|
#[ORM\OneToMany(targetEntity: Weight::class, mappedBy: 'reception', cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||||
#[Groups(['reception:read'])]
|
#[Groups(['reception:read'])]
|
||||||
private Collection $weights;
|
private Collection $weights;
|
||||||
@@ -106,6 +110,28 @@ class Reception
|
|||||||
#[ApiProperty(readableLink: true)]
|
#[ApiProperty(readableLink: true)]
|
||||||
private ?ReceptionType $receptionType = null;
|
private ?ReceptionType $receptionType = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'receptions')]
|
||||||
|
#[ORM\JoinColumn(nullable: true)]
|
||||||
|
#[Groups(['reception:read', 'reception:write'])]
|
||||||
|
#[ApiProperty(readableLink: true)]
|
||||||
|
private ?MerchandiseType $merchandiseType = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<int, Building>
|
||||||
|
*/
|
||||||
|
#[ORM\ManyToMany(targetEntity: Building::class, inversedBy: 'receptions')]
|
||||||
|
#[ORM\JoinTable(name: 'reception_building')]
|
||||||
|
#[Groups(['reception:read', 'reception:write'])]
|
||||||
|
#[ApiProperty(readableLink: true)]
|
||||||
|
private Collection $buildings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Collection<int, ReceptionPelletBuilding>
|
||||||
|
*/
|
||||||
|
#[ORM\OneToMany(targetEntity: ReceptionPelletBuilding::class, mappedBy: 'reception', cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||||
|
#[Groups(['reception:read'])]
|
||||||
|
private Collection $pelletBuildings;
|
||||||
|
|
||||||
#[ORM\ManyToOne]
|
#[ORM\ManyToOne]
|
||||||
#[ORM\JoinColumn(nullable: true)]
|
#[ORM\JoinColumn(nullable: true)]
|
||||||
#[Groups(['reception:read', 'reception:write'])]
|
#[Groups(['reception:read', 'reception:write'])]
|
||||||
@@ -147,6 +173,8 @@ class Reception
|
|||||||
) {
|
) {
|
||||||
$this->receptionDate = $receptionDate;
|
$this->receptionDate = $receptionDate;
|
||||||
$this->weights = new ArrayCollection();
|
$this->weights = new ArrayCollection();
|
||||||
|
$this->buildings = new ArrayCollection();
|
||||||
|
$this->pelletBuildings = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
@@ -218,6 +246,18 @@ class Reception
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getMerchandiseDetail(): ?string
|
||||||
|
{
|
||||||
|
return $this->merchandiseDetail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMerchandiseDetail(?string $merchandiseDetail): self
|
||||||
|
{
|
||||||
|
$this->merchandiseDetail = $merchandiseDetail;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return Collection<int, Weight>
|
* @return Collection<int, Weight>
|
||||||
*/
|
*/
|
||||||
@@ -238,6 +278,71 @@ class Reception
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getMerchandiseType(): ?MerchandiseType
|
||||||
|
{
|
||||||
|
return $this->merchandiseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setMerchandiseType(?MerchandiseType $merchandiseType): self
|
||||||
|
{
|
||||||
|
$this->merchandiseType = $merchandiseType;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, Building>
|
||||||
|
*/
|
||||||
|
public function getBuildings(): Collection
|
||||||
|
{
|
||||||
|
return $this->buildings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addBuilding(Building $building): self
|
||||||
|
{
|
||||||
|
if (!$this->buildings->contains($building)) {
|
||||||
|
$this->buildings->add($building);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeBuilding(Building $building): self
|
||||||
|
{
|
||||||
|
$this->buildings->removeElement($building);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection<int, ReceptionPelletBuilding>
|
||||||
|
*/
|
||||||
|
public function getPelletBuildings(): Collection
|
||||||
|
{
|
||||||
|
return $this->pelletBuildings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addPelletBuilding(ReceptionPelletBuilding $pelletBuilding): self
|
||||||
|
{
|
||||||
|
if (!$this->pelletBuildings->contains($pelletBuilding)) {
|
||||||
|
$this->pelletBuildings->add($pelletBuilding);
|
||||||
|
$pelletBuilding->setReception($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removePelletBuilding(ReceptionPelletBuilding $pelletBuilding): self
|
||||||
|
{
|
||||||
|
if ($this->pelletBuildings->removeElement($pelletBuilding)) {
|
||||||
|
if ($pelletBuilding->getReception() === $this) {
|
||||||
|
$pelletBuilding->setReception(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function getUser(): ?User
|
public function getUser(): ?User
|
||||||
{
|
{
|
||||||
return $this->user;
|
return $this->user;
|
||||||
|
|||||||
101
src/Entity/ReceptionPelletBuilding.php
Normal file
101
src/Entity/ReceptionPelletBuilding.php
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||||
|
use ApiPlatform\Metadata\ApiFilter;
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
|
#[ORM\Entity]
|
||||||
|
#[ORM\Table(name: 'reception_pellet_building')]
|
||||||
|
#[ORM\UniqueConstraint(name: 'uniq_reception_pellet_building', columns: ['reception_id', 'pellet_type_id', 'building_id'])]
|
||||||
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new Get(
|
||||||
|
requirements: ['id' => '\d+'],
|
||||||
|
normalizationContext: ['groups' => ['reception-pellet-building:read']],
|
||||||
|
),
|
||||||
|
new GetCollection(
|
||||||
|
normalizationContext: ['groups' => ['reception-pellet-building:read']],
|
||||||
|
),
|
||||||
|
new Post(
|
||||||
|
normalizationContext: ['groups' => ['reception-pellet-building:read']],
|
||||||
|
denormalizationContext: ['groups' => ['reception-pellet-building:write']],
|
||||||
|
),
|
||||||
|
new Delete(),
|
||||||
|
],
|
||||||
|
security: "is_granted('ROLE_USER')",
|
||||||
|
)]
|
||||||
|
#[ApiFilter(SearchFilter::class, properties: ['reception' => 'exact'])]
|
||||||
|
class ReceptionPelletBuilding
|
||||||
|
{
|
||||||
|
#[ORM\Id]
|
||||||
|
#[ORM\GeneratedValue]
|
||||||
|
#[ORM\Column]
|
||||||
|
#[Groups(['reception-pellet-building:read', 'reception:read'])]
|
||||||
|
private ?int $id = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne(inversedBy: 'pelletBuildings')]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
#[Groups(['reception-pellet-building:read', 'reception-pellet-building:write'])]
|
||||||
|
private ?Reception $reception = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
#[Groups(['reception-pellet-building:read', 'reception-pellet-building:write', 'reception:read'])]
|
||||||
|
private ?PelletType $pelletType = null;
|
||||||
|
|
||||||
|
#[ORM\ManyToOne]
|
||||||
|
#[ORM\JoinColumn(nullable: false)]
|
||||||
|
#[Groups(['reception-pellet-building:read', 'reception-pellet-building:write', 'reception:read'])]
|
||||||
|
private ?Building $building = null;
|
||||||
|
|
||||||
|
public function getId(): ?int
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getReception(): ?Reception
|
||||||
|
{
|
||||||
|
return $this->reception;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setReception(?Reception $reception): self
|
||||||
|
{
|
||||||
|
$this->reception = $reception;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPelletType(): ?PelletType
|
||||||
|
{
|
||||||
|
return $this->pelletType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPelletType(?PelletType $pelletType): self
|
||||||
|
{
|
||||||
|
$this->pelletType = $pelletType;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBuilding(): ?Building
|
||||||
|
{
|
||||||
|
return $this->building;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBuilding(?Building $building): self
|
||||||
|
{
|
||||||
|
$this->building = $building;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,12 +16,10 @@
|
|||||||
p{ margin:0; }
|
p{ margin:0; }
|
||||||
em{ font-style: normal; }
|
em{ font-style: normal; }
|
||||||
|
|
||||||
.red{ color:red; }
|
|
||||||
|
|
||||||
.company-block{
|
.company-block{
|
||||||
font-size:14px;
|
font-size:14px;
|
||||||
text-align:left;
|
text-align:left;
|
||||||
line-height:1.25;
|
line-height:1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.box{
|
.box{
|
||||||
@@ -36,7 +34,7 @@
|
|||||||
text-align:center;
|
text-align:center;
|
||||||
font-size: 18pt;
|
font-size: 18pt;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin: 64px 0 15px 0;
|
margin: 64px 0 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-table {
|
.info-table {
|
||||||
@@ -63,30 +61,60 @@
|
|||||||
}
|
}
|
||||||
th{ text-align:center; font-weight:700; }
|
th{ text-align:center; font-weight:700; }
|
||||||
|
|
||||||
/* tables de layout (sans bordures) */
|
|
||||||
.layout, .layout td{ border:none !important; padding:0; }
|
.layout, .layout td{ border:none !important; padding:0; }
|
||||||
|
|
||||||
/* GRAND TABLEAU : verrouillage dompdf */
|
.bigtable-wrap{
|
||||||
|
border: 1px solid #000;
|
||||||
|
height: 425px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.bigtable{
|
.bigtable{
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
border-collapse: collapse;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bigtable th,
|
.bigtable th,
|
||||||
.bigtable td{
|
.bigtable td{
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
border: 1px solid #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bigtable thead th{ border-top: 0; }
|
||||||
|
.bigtable tbody tr:last-child td{ border-bottom: 0; }
|
||||||
|
|
||||||
|
.bigtable tr th:first-child,
|
||||||
|
.bigtable tr td:first-child{ border-left: 0; }
|
||||||
|
|
||||||
|
.bigtable tr th:last-child,
|
||||||
|
.bigtable tr td:last-child{ border-right: 0; }
|
||||||
|
|
||||||
|
.bigtable thead th{ border-bottom: 0; }
|
||||||
|
.bigtable tbody tr:first-child td{ border-top: 1px solid #333; }
|
||||||
|
|
||||||
.bigtable-notes{
|
.bigtable-notes{
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ligne “filler” pour forcer la hauteur comme l'exemple */
|
.border-bottom {
|
||||||
.fill td{
|
border-bottom: 1px solid #000;
|
||||||
border-top:none;
|
|
||||||
height: 75mm; /* ajuste si besoin */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer-block{ page-break-inside: avoid; }
|
||||||
|
|
||||||
.signature-title{ font-size:12px; margin-bottom:2mm; }
|
.signature-title{ font-size:12px; margin-bottom:2mm; }
|
||||||
.signature-box{ height: 22mm; margin-bottom: 4mm; }
|
.signature-box {
|
||||||
|
height: 22mm;
|
||||||
|
margin-bottom: 4mm;
|
||||||
|
border: 0.5px solid #000;
|
||||||
|
|
||||||
|
padding: 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.meta{ font-size: 16px; line-height: 1.35; }
|
.meta{ font-size: 16px; line-height: 1.35; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
@@ -104,7 +132,7 @@
|
|||||||
14 Allée d’Argenson<br>
|
14 Allée d’Argenson<br>
|
||||||
Z.I Nord – Secteur Est<br>
|
Z.I Nord – Secteur Est<br>
|
||||||
86100 CHATELLERAULT<br>
|
86100 CHATELLERAULT<br>
|
||||||
TEL : 05 49 20 09 10<br>
|
Tel. : 05 49 20 09 10<br>
|
||||||
Email : lpc.contacts@lpc-liot.fr<br>
|
Email : lpc.contacts@lpc-liot.fr<br>
|
||||||
RCS Châtellerault B 444 262 455
|
RCS Châtellerault B 444 262 455
|
||||||
</td>
|
</td>
|
||||||
@@ -113,7 +141,7 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td style="width:30%; text-align:right; vertical-align:top; font-size: 14px;">
|
<td style="width:30%; text-align:right; vertical-align:top; font-size: 14px;">
|
||||||
<div style="display:inline-block; width:75mm;">
|
<div style="display:inline-block; width:75mm; line-height:1.3;">
|
||||||
<strong>{{ reception.supplier.name }}</strong><br>
|
<strong>{{ reception.supplier.name }}</strong><br>
|
||||||
<span>{{ reception.address.street }}</span><br>
|
<span>{{ reception.address.street }}</span><br>
|
||||||
<span>{{ reception.address.postalCode }} {{ reception.address.city }}</span>
|
<span>{{ reception.address.postalCode }} {{ reception.address.city }}</span>
|
||||||
@@ -140,20 +168,20 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
<!-- GRAND TABLEAU -->
|
<!-- GRAND TABLEAU -->
|
||||||
<table class="bigtable" style="margin-bottom:10px; width:100%; border-collapse:collapse; table-layout:fixed;">
|
<div class="bigtable-wrap">
|
||||||
|
<table class="bigtable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:75%; text-align:center;">Désignation</th>
|
<th style="width:75%; text-align:center;">Désignation</th>
|
||||||
<th style="width:25%; text-align:right; white-space:nowrap;">Qté livrée (kg)</th>
|
<th style="width:25%; text-align:center; white-space:nowrap;">Qté livrée (kg)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width:75%;">
|
<td style="width:75%;">
|
||||||
<strong class="red">MAÏS sec</strong><br><br>
|
<strong>{{ reception.receptionType.label }}</strong><br><br>
|
||||||
|
|
||||||
<div class="bigtable-notes">
|
<div class="bigtable-notes">
|
||||||
{% set grossWeight = null %}
|
{% set grossWeight = null %}
|
||||||
@@ -171,7 +199,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td style="width:25%; text-align:right; white-space:nowrap;">
|
<td style="width:25%; text-align:center; white-space:nowrap;">
|
||||||
{% if grossWeight and tareWeight %}
|
{% if grossWeight and tareWeight %}
|
||||||
{{ grossWeight.weight - tareWeight.weight }}
|
{{ grossWeight.weight - tareWeight.weight }}
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -180,17 +208,60 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<!-- filler : garde le grand bloc haut comme sur l'exemple -->
|
<tr class="border-bottom">
|
||||||
<tr class="fill">
|
<td>
|
||||||
<td style="width:75%;"></td>
|
<strong>
|
||||||
<td style="width:25%;"></td>
|
{% if reception.merchandiseType %}
|
||||||
|
{{ reception.merchandiseType.label }}
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</strong>
|
||||||
|
<br><br>
|
||||||
|
|
||||||
|
<div class="bigtable-notes">
|
||||||
|
{% if reception.merchandiseType and reception.merchandiseType.code == 'AUTRES' and reception.merchandiseDetail %}
|
||||||
|
<p><strong>Précision</strong> : {{ reception.merchandiseDetail }}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if reception.merchandiseType and reception.merchandiseType.code == 'GRANULE' %}
|
||||||
|
{% set pelletGroups = {} %}
|
||||||
|
{% for selection in reception.pelletBuildings %}
|
||||||
|
{% set pelletLabel = selection.pelletType.label %}
|
||||||
|
{% if pelletGroups[pelletLabel] is not defined %}
|
||||||
|
{% set pelletGroups = pelletGroups|merge({ (pelletLabel): [] }) %}
|
||||||
|
{% endif %}
|
||||||
|
{% set pelletGroups = pelletGroups|merge({
|
||||||
|
(pelletLabel): pelletGroups[pelletLabel]|merge([selection.building.label])
|
||||||
|
}) %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% for pelletLabel, buildingLabels in pelletGroups %}
|
||||||
|
<p><strong>{{ pelletLabel }}</strong> : {{ buildingLabels|join(', ') }}</p>
|
||||||
|
{% else %}
|
||||||
|
<p>Aucun dépôt de granulés renseigné.</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
{% set buildingLabels = [] %}
|
||||||
|
{% for building in reception.buildings %}
|
||||||
|
{% set buildingLabels = buildingLabels|merge([building.label]) %}
|
||||||
|
{% endfor %}
|
||||||
|
{% if buildingLabels %}
|
||||||
|
<p><strong>Ferme</strong> : {{ buildingLabels|join(', ') }}</p>
|
||||||
|
{% else %}
|
||||||
|
<p>Aucun bâtiment renseigné.</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BAS : meta à gauche / signatures à droite -->
|
||||||
<!-- BAS : meta à gauche / signatures à droite (sans float) -->
|
<table class="layout footer-block">
|
||||||
<table class="layout">
|
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width:60%; padding-right:8mm; vertical-align:top;">
|
<td style="width:60%; padding-right:8mm; vertical-align:top;">
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
|
|||||||
Reference in New Issue
Block a user