Finalisation réception marchandise, ajout de composant UI et ajout de fixtures #7

Merged
tristan merged 10 commits from feat/finalisation-reception-marchandise into develop 2026-01-30 14:10:41 +00:00
10 changed files with 213 additions and 191 deletions
Showing only changes of commit cff80b5ab2 - Show all commits

82
.idea/workspace.xml generated
View File

@@ -4,24 +4,17 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : update du CHANGELOG.md">
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : finalisation de l'étape 1 &quot;Réception&quot; (formulaire)">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/AGENTS.md" beforeDir="false" afterPath="$PROJECT_DIR$/AGENTS.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/reception/reception-form.vue" 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/ui/license-plate-input.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/components/ui/license-plate-input.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/composables/useWeighing.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/composables/useWeighing.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/layouts/default.vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/layouts/default.vue" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/nuxt.config.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/nuxt.config.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/pages/reception/[[id]].vue" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/pages/reception/[[id]].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/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/user-data.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/services/dto/user-data.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/services/reception.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/services/reception.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/stores/reception.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/stores/reception.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/frontend/tailwind.config.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/tailwind.config.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/User.php" beforeDir="false" afterPath="$PROJECT_DIR$/src/Entity/User.php" afterDir="false" />
<change beforePath="$PROJECT_DIR$/templates/reception_voucher.html.twig" beforeDir="false" afterPath="$PROJECT_DIR$/templates/reception_voucher.html.twig" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -225,34 +218,34 @@
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"RunOnceActivity.MCP Project settings loaded": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
"RunOnceActivity.git.unshallow": "true",
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
"git-widget-placeholder": "feat/finalisation-reception-marchandise",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "preferences.keymap",
"vue.rearranger.settings.migration": "true"
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;RunOnceActivity.MCP Project settings loaded&quot;: &quot;true&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;RunOnceActivity.typescript.service.memoryLimit.init&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;feat/finalisation-reception-marchandise&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.keymap&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
},
"keyToStringList": {
"DatabaseDriversLRU": [
"postgresql"
&quot;keyToStringList&quot;: {
&quot;DatabaseDriversLRU&quot;: [
&quot;postgresql&quot;
],
"com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
"TEXT"
&quot;com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File&quot;: [
&quot;TEXT&quot;
],
"vue.recent.templates": [
"Vue Composition API Component"
&quot;vue.recent.templates&quot;: [
&quot;Vue Composition API Component&quot;
]
}
}]]></component>
}</component>
<component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS">
<recent name="\\wsl.localhost\Ubuntu-24.04\home\m-tristan\workspace\Ferme" />
@@ -284,7 +277,8 @@
<workItem from="1768547023783" duration="11371000" />
<workItem from="1768894030675" duration="83922000" />
<workItem from="1769413136483" duration="58000" />
<workItem from="1769413279223" duration="37300000" />
<workItem from="1769413279223" duration="40490000" />
<workItem from="1769612160652" duration="12834000" />
</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)">
<option name="closed" value="true" />
@@ -574,7 +568,15 @@
<option name="project" value="LOCAL" />
<updated>1769435038236</updated>
</task>
<option name="localTasksCounter" value="37" />
<task id="LOCAL-00037" summary="feat : finalisation de l'étape 1 &quot;Réception&quot; (formulaire)">
<option name="closed" value="true" />
<created>1769529522614</created>
<option name="number" value="00037" />
<option name="presentableId" value="LOCAL-00037" />
<option name="project" value="LOCAL" />
<updated>1769529522614</updated>
</task>
<option name="localTasksCounter" value="38" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -624,7 +626,6 @@
</option>
</component>
<component name="VcsManagerConfiguration">
<MESSAGE value="feat : ajout d'une gestion d'erreur au global côté front avec la lib toaster et I18n pour centraliser les messages d'erreur" />
<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="feat : ajout de la conf pour le déploiement en recette" />
@@ -649,7 +650,8 @@
<MESSAGE value="feat : ajout du bundle Malio ednotif pour l'utilisation des WS" />
<MESSAGE value="fix : modification de la conf du bundle ednotif" />
<MESSAGE value="feat : update du CHANGELOG.md" />
<option name="LAST_COMMIT_MESSAGE" value="feat : update du CHANGELOG.md" />
<MESSAGE value="feat : finalisation de l'étape 1 &quot;Réception&quot; (formulaire)" />
<option name="LAST_COMMIT_MESSAGE" value="feat : finalisation de l'étape 1 &quot;Réception&quot; (formulaire)" />
</component>
<component name="XSLT-Support.FileAssociations.UIState">
<expand />

View File

@@ -21,6 +21,7 @@ Frontend conventions
- Nuxt SSR disabled; Tailwind used.
- Layout in `frontend/layouts/default.vue`: max width `1050px`, header full width.
- Tailwind custom color palette is `primary` (e.g. `bg-primary-500`).
- Global font stack uses Helvetica via Tailwind (`font-sans`) and `frontend/assets/css/main.css`.
- API composable in `frontend/composables/useApi.ts` with `get/post/put/patch/delete` and default JSON/PATCH content types.
- API errors/success toasts can be customized via `toastErrorMessage`/`toastSuccessMessage` or i18n keys `toastErrorKey`/`toastSuccessKey`. Global method fallbacks use `errors.http.*` keys.
- `useApi` uses `useNuxtApp().$i18n` (not `useI18n`) to avoid setup-only constraint in service calls.
@@ -44,3 +45,11 @@ Environment & routing
Notes
- Do not add a GET that creates resources; use POST + PATCH.
- Keep endpoints in plural (API Platform convention).
- New reference data added:
- Reception types (`reception_type`, fields: `label`, `code`), selectable on reception form.
- 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.
- Carriers (`carrier`, fields: `name`, nullable `code`), Drivers (`driver`, fields: `name`, `carrier_id`), Vehicles (`vehicle`, fields: `plate`, `carrier_id`, `truck_id`) used for LIOT logic.
- Reception links: `reception_type_id`, `supplier_id`, `address_id`, `truck_id`, `carrier_id`, `driver_id`, `user_id`.
- Address exposes `fullAddress` via getter for display.
- LIOT behavior in reception form: if carrier code = `LIOT`, show driver + vehicle selects and hide manual license plate input; vehicle list filters by truck type and carrier; selected vehicle sets `license_plate`.

View File

@@ -33,14 +33,13 @@
@click="printReceipt"
>Générer le bon</button>
</div>
<UiPdfPrinter ref="pdfPrinter" />
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useWeighing } from '~/composables/useWeighing'
import { usePdfPrinter } from '~/composables/usePdfPrinter'
import { useReceptionStore } from '~/stores/reception'
const props = defineProps<{
@@ -50,14 +49,9 @@ const props = defineProps<{
const router = useRouter()
const receptionStore = useReceptionStore()
const { current: storeReception } = storeToRefs(receptionStore)
type PdfPrinterHandle = {
print: (url: string) => Promise<void>
}
// Ref sur le composant d'impression pour déclencher le print() du PDF.
const pdfPrinter = ref<PdfPrinterHandle | null>(null)
const { printPdf } = usePdfPrinter()
const {
displayWeight,
displayDsd,
title,
showLoadingBox,
fetchWeight,
@@ -73,12 +67,12 @@ const showGenerateReceipt = computed(
)
const printReceipt = async () => {
if (!import.meta.client || !receptionStore.current || !pdfPrinter.value) {
if (!import.meta.client || !receptionStore.current) {
return
}
await saveWeight()
await pdfPrinter.value.print(`/receptions/${receptionStore.current.id}/receipt`)
await printPdf(`/receptions/${receptionStore.current.id}/receipt`)
// Laisse le temps a la boite de dialogue d'impression de s'ouvrir.
await new Promise((resolve) => setTimeout(resolve, 600))

View File

@@ -1,20 +0,0 @@
<template>
<iframe ref="printFrame" class="hidden" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { usePdfPrinter } from '~/composables/usePdfPrinter'
const printFrame = ref<HTMLIFrameElement | null>(null)
const { printPdf } = usePdfPrinter()
// Expose une methode simple pour imprimer un PDF depuis les ecrans.
const print = async (url: string): Promise<void> => {
return printPdf(url, printFrame)
}
defineExpose({
print
})
</script>

View File

@@ -1,37 +1,26 @@
import type { Ref } from 'vue'
import { useApi } from '~/composables/useApi'
type PrintFrameRef = Ref<HTMLIFrameElement | null>
import {useApi} from '~/composables/useApi'
export const usePdfPrinter = () => {
const api = useApi()
const receptionStore = useReceptionStore()
const currentReception = receptionStore.current
const printPdf = async (url: string, frameRef: PrintFrameRef): Promise<void> => {
const printPdf = async (url: string): Promise<void> => {
if (!import.meta.client) {
return
}
const frame = frameRef.value
if (!frame) {
return
}
// On charge le PDF en blob pour rester en same-origin dans l'iframe.
const blob = await api.getBlob(url)
const blobUrl = URL.createObjectURL(blob)
const tryPrint = () => {
frame.contentWindow?.focus()
frame.contentWindow?.print()
}
frame.onload = () => {
tryPrint()
// On libere l'URL blob apres l'impression.
setTimeout(() => URL.revokeObjectURL(blobUrl), 2000)
}
frame.src = blobUrl
setTimeout(tryPrint, 1200)
const a = document.createElement('a');
a.href = url;
// nom du fichier à changer par les infos du store pinia
a.download = `${currentReception.identificationNumber}_${currentReception.supplier.name}_${currentReception.licensePlate}`;
document.body.appendChild(a);
a.click();
a.remove();
window.open(blobUrl, '_blank', 'noopener,noreferrer')
setTimeout(() => URL.revokeObjectURL(blobUrl), 60000)
}
return {

View File

@@ -8,6 +8,7 @@ import type { DriverData } from '~/services/dto/driver-data'
export interface ReceptionData {
id: number
identificationNumber?: string | null
licensePlate: string | null
weights?: WeightEntryData[] | null
receptionDate: string

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20260128000100 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add identification number to receptions';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE reception ADD identification_number VARCHAR(20) DEFAULT NULL');
$this->addSql("UPDATE reception SET identification_number = 'N-BR-' || LPAD(id::text, 4, '0') WHERE identification_number IS NULL");
$this->addSql('CREATE UNIQUE INDEX UNIQ_reception_identification_number ON reception (identification_number)');
}
public function down(Schema $schema): void
{
$this->addSql('DROP INDEX UNIQ_reception_identification_number');
$this->addSql('ALTER TABLE reception DROP identification_number');
}
}

View File

@@ -35,19 +35,19 @@ class Address
private ?int $id = null;
#[ORM\Column(length: 120)]
#[Groups(['address:read', 'supplier:read'])]
#[Groups(['address:read', 'supplier:read', 'reception:read'])]
private string $label = '';
#[ORM\Column(length: 180)]
#[Groups(['address:read', 'supplier:read'])]
#[Groups(['address:read', 'supplier:read', 'reception:read'])]
private string $street = '';
#[ORM\Column(name: 'postal_code', length: 20)]
#[Groups(['address:read', 'supplier:read'])]
#[Groups(['address:read', 'supplier:read', 'reception:read'])]
private string $postalCode = '';
#[ORM\Column(length: 120)]
#[Groups(['address:read', 'supplier:read'])]
#[Groups(['address:read', 'supplier:read', 'reception:read'])]
private string $city = '';
#[ORM\Column(name: 'country_code', length: 2)]

View File

@@ -17,6 +17,7 @@ use App\State\ReceptionWeighingProvider;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Event\PostPersistEventArgs;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Attribute\Groups;
@@ -78,6 +79,10 @@ class Reception
#[Groups(['reception:read', 'reception:write'])]
private ?string $licensePlate = null;
#[ORM\Column(length: 20, unique: true, nullable: true)]
#[Groups(['reception:read'])]
private ?string $identificationNumber = null;
#[ORM\Column(options: ['default' => 0])]
#[Groups(['reception:read', 'reception:write'])]
private int $currentStep = 0;
@@ -155,6 +160,18 @@ class Reception
return $this->licensePlate;
}
public function getIdentificationNumber(): ?string
{
return $this->identificationNumber;
}
public function setIdentificationNumber(?string $identificationNumber): self
{
$this->identificationNumber = $identificationNumber;
return $this;
}
public function setLicensePlate(?string $licensePlate): self
{
$this->licensePlate = $licensePlate;
@@ -321,4 +338,30 @@ class Reception
$this->receptionDate = new DateTimeImmutable();
}
}
#[ORM\PostPersist]
public function initializeIdentificationNumber(PostPersistEventArgs $args): void
{
if (null !== $this->identificationNumber) {
return;
}
if (null === $this->id) {
return;
}
$number = sprintf('N-BR-%04d', $this->id);
$this->identificationNumber = $number;
$args->getObjectManager()
->getConnection()
->executeStatement(
'UPDATE reception SET identification_number = :number WHERE id = :id',
[
'number' => $number,
'id' => $this->id,
]
)
;
}
}

View File

@@ -7,7 +7,7 @@
@page { margin: 56px 56px; }
body{
font-family: Arial, sans-serif;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 13px;
margin:0;
color:#000;
@@ -19,7 +19,7 @@
.red{ color:red; }
.company-block{
font-size:13px;
font-size:14px;
text-align:left;
line-height:1.25;
}
@@ -36,15 +36,30 @@
text-align:center;
font-size: 18pt;
font-weight: 700;
margin: 0 0 4mm 0;
margin: 64px 0 15px 0;
}
.info-table {
margin-bottom: 32px;
width:100%;
border-collapse:collapse;
table-layout:fixed;
}
.info-table th {
font-size: 16px;
}
table{
width:100%;
border-collapse: collapse;
}
table{ width:100%; border-collapse: collapse; }
th, td{
border:1px solid #333;
padding:4px 6px;
vertical-align: top;
font-size: 9pt;
font-size: 12px;
}
th{ text-align:center; font-weight:700; }
@@ -52,7 +67,17 @@
.layout, .layout td{ border:none !important; padding:0; }
/* GRAND TABLEAU : verrouillage dompdf */
.bigtable{ table-layout: fixed; }
.bigtable{
table-layout: fixed;
}
.bigtable th,
.bigtable td{
font-size: 16px;
}
.bigtable-notes{
font-size: 14px;
line-height: 1.25;
}
/* ligne “filler” pour forcer la hauteur comme l'exemple */
.fill td{
@@ -60,32 +85,9 @@
height: 75mm; /* ajuste si besoin */
}
/* Bloc IDTF comme lexemple */
table.idtf{
width: 350px; /* ou 100% si tu préfères */
border-collapse: collapse;
border: 1px solid #333 !important; /* bordure extérieure */
margin-top: 12px; /* ~3mm */
font-size: 8.5pt;
table-layout: fixed;
}
/* IMPORTANT: on cible td DANS table.idtf et on force */
table.idtf td{
border: 1px solid #333 !important;
padding: 3px 5px;
vertical-align: top;
}
/* Largeurs en % (pas de mm) */
table.idtf td.n{ width: 8%; text-align:center; }
table.idtf td.prod{ width: 52%; }
table.idtf td.net{ width: 20%; }
table.idtf td.date{ width: 20%; }
.signature-title{ font-size:9pt; margin-bottom:2mm; }
.signature-title{ font-size:12px; margin-bottom:2mm; }
.signature-box{ height: 22mm; margin-bottom: 4mm; }
.meta{ font-size: 9pt; line-height: 1.35; }
.meta{ font-size: 16px; line-height: 1.35; }
</style>
</head>
@@ -95,33 +97,26 @@
<table class="layout" style="width:100%;">
<tr>
<td style="width:70%; vertical-align:top;">
<!-- table imbriquée : 1 ligne logo, 1 ligne texte (dompdf-friendly) -->
<table class="layout" style="width:100%;">
<tr>
<td style="padding:0; border:none;">
<img src="https://static.mixsuite.fr/liot/logo.png"
style="width:110px; display:block; margin:0 0 4mm 0;">
</td>
</tr>
<tr>
<td class="company-block" style="padding:0; border:none;">
<strong>SA LIOT Châtellerault</strong><br>
Site de <b>Châtellerault</b><br>
<strong>SCEA LES NAUDS</strong><br>
14 Allée dArgenson<br>
Z.I Nord Secteur Est<br>
86100 CHATELLERAULT<br>
TEL : 05 49 20 09 10 Fax : 05 49 85 37 82<br>
TEL : 05 49 20 09 10<br>
Email : lpc.contacts@lpc-liot.fr<br>
RCS Châtellerault B 339 505 612
RCS Châtellerault B 444 262 455
</td>
</tr>
</table>
</td>
<td style="width:30%; text-align:left; vertical-align:top;">
<div class="box" style="display:inline-block; width:75mm;">
<strong class="red">Nom de l'entreprise</strong><br><br><br>
<span class="red">Adresse de l'entreprise</span>
<td style="width:30%; text-align:right; vertical-align:top; font-size: 14px;">
<div style="display:inline-block; width:75mm;">
<strong>{{ reception.supplier.name }}</strong><br>
<span>{{ reception.address.street }}</span><br>
<span>{{ reception.address.postalCode }} {{ reception.address.city }}</span>
</div>
</td>
</tr>
@@ -130,18 +125,18 @@
<div class="title">BON DE RECEPTION</div>
<!-- INFOS (code/date/num) -->
<table style="margin-bottom:3mm; width:100%; border-collapse:collapse; table-layout:fixed;">
<table class="info-table">
<tr>
<th style="width:60%; text-align:center;">Code fournisseur</th>
<th style="width:55%; text-align:center;">Code fournisseur</th>
<th style="width:20%; text-align:center; white-space:nowrap;">Date</th>
<th style="width:20%; text-align:center; white-space:nowrap;">N° réception</th>
<th style="width:25%; text-align:center; white-space:nowrap;">N° réception</th>
</tr>
<tr>
<td class="red" style="width:60%; text-align:center;">XXX</td>
<td style="width:55%; text-align:center;">{{ reception.supplier.name }}</td>
<td style="width:20%; text-align:center; white-space:nowrap;">
{{ reception.receptionDate|date('d/m/Y') }}
</td>
<td class="red" style="width:20%; text-align:center; white-space:nowrap;">86-BR-XXXX</td>
<td style="width:25%; text-align:center; white-space:nowrap;">{{ reception.identificationNumber }}</td>
</tr>
</table>
@@ -150,20 +145,17 @@
<table class="bigtable" style="margin-bottom:10px; width:100%; border-collapse:collapse; table-layout:fixed;">
<thead>
<tr>
<th style="width:15%; text-align:center;">Code</th>
<th style="width:65%; text-align:center;">Désignation</th>
<th style="width:20%; text-align:right; white-space:nowrap;">Qté livrée (kg)</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>
</tr>
</thead>
<tbody>
<tr>
<td class="red" style="width:12%;">M</td>
<td style="width:68%;">
<td style="width:75%;">
<strong class="red">MAÏS sec</strong><br><br>
<div style="font-size:8.5pt; line-height:1.25;">
<div class="bigtable-notes">
{% set grossWeight = null %}
{% set tareWeight = null %}
@@ -179,7 +171,7 @@
</div>
</td>
<td class="red" style="width:20%; text-align:right; white-space:nowrap;">
<td style="width:25%; text-align:right; white-space:nowrap;">
{% if grossWeight and tareWeight %}
{{ grossWeight.weight - tareWeight.weight }}
{% else %}
@@ -190,9 +182,8 @@
<!-- filler : garde le grand bloc haut comme sur l'exemple -->
<tr class="fill">
<td style="width:15%;"></td>
<td style="width:65%;"></td>
<td style="width:20%;"></td>
<td style="width:75%;"></td>
<td style="width:25%;"></td>
</tr>
</tbody>
</table>
@@ -202,32 +193,16 @@
<table class="layout">
<tr>
<td style="width:60%; padding-right:8mm; vertical-align:top;">
<div class="meta red">
Transporteur : <strong class="red">Nom du transporteur</strong><br>
Mode de livraison : <strong class="red">Fond-mouvant</strong><br>
Immatriculation : <strong class="red">{{ reception.licensePlate }}</strong><br><br>
Poids annoncé : <strong class="red">XXXXX kg</strong>
<div class="meta">
Transporteur : <strong>{{ reception.carrier.name }}</strong><br>
Mode de livraison : <strong>{{ reception.truck.name }}</strong><br>
Immatriculation : <strong>{{ reception.licensePlate }}</strong><br><br>
</div>
<!-- Bloc IDTF -->
<table class="idtf">
<tr>
<td class="n">1</td>
<td class="prod red">
Produit : <span class="red">Nom du produit</span><br>
N° IDTF : <span class="red">4000XX</span>
</td>
<td class="net red">Nettoyage : <span class="red">A</span></td>
<td class="date red">Date : <span class="red">14/01/2026</span></td>
</tr>
<!-- répète le <tr> si besoin -->
</table>
</td>
<td style="width:40%; vertical-align:top;">
<div class="signature-title">Signature :</div>
<div class="box signature-box">Ets Liot :</div>
<div class="box signature-box">Les Nauds :</div>
<div class="box signature-box">Transporteur :</div>
</td>
</tr>