Finalisation réception marchandise, ajout de composant UI et ajout de fixtures #7
82
.idea/workspace.xml
generated
82
.idea/workspace.xml
generated
@@ -4,24 +4,17 @@
|
|||||||
<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 : update du CHANGELOG.md">
|
<list default="true" id="7c107abe-5995-4428-8429-b146aaca8386" name="Changes" comment="feat : finalisation de l'étape 1 "Réception" (formulaire)">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/AGENTS.md" beforeDir="false" afterPath="$PROJECT_DIR$/AGENTS.md" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/config/reference.php" beforeDir="false" afterPath="$PROJECT_DIR$/config/reference.php" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/frontend/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/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/components/ui/pdf-printer.vue" beforeDir="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/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/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/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$/frontend/services/dto/user-data.ts" beforeDir="false" afterPath="$PROJECT_DIR$/frontend/services/dto/user-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$/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/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$/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>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -225,34 +218,34 @@
|
|||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
<option name="showLibraryContents" value="true" />
|
<option name="showLibraryContents" value="true" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent"><![CDATA[{
|
<component name="PropertiesComponent">{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
"RunOnceActivity.MCP Project settings loaded": "true",
|
"RunOnceActivity.MCP Project settings loaded": "true",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
||||||
"git-widget-placeholder": "feat/finalisation-reception-marchandise",
|
"git-widget-placeholder": "feat/finalisation-reception-marchandise",
|
||||||
"node.js.detected.package.eslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.tslint": "true",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
"nodejs_package_manager_path": "npm",
|
"nodejs_package_manager_path": "npm",
|
||||||
"settings.editor.selected.configurable": "preferences.keymap",
|
"settings.editor.selected.configurable": "preferences.keymap",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"vue.rearranger.settings.migration": "true"
|
||||||
},
|
},
|
||||||
"keyToStringList": {
|
"keyToStringList": {
|
||||||
"DatabaseDriversLRU": [
|
"DatabaseDriversLRU": [
|
||||||
"postgresql"
|
"postgresql"
|
||||||
],
|
],
|
||||||
"com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
|
"com.intellij.ide.scratch.ScratchImplUtil$2/New Scratch File": [
|
||||||
"TEXT"
|
"TEXT"
|
||||||
],
|
],
|
||||||
"vue.recent.templates": [
|
"vue.recent.templates": [
|
||||||
"Vue Composition API Component"
|
"Vue Composition API Component"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}]]></component>
|
}</component>
|
||||||
<component name="RecentsManager">
|
<component name="RecentsManager">
|
||||||
<key name="MoveFile.RECENT_KEYS">
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
<recent name="\\wsl.localhost\Ubuntu-24.04\home\m-tristan\workspace\Ferme" />
|
<recent name="\\wsl.localhost\Ubuntu-24.04\home\m-tristan\workspace\Ferme" />
|
||||||
@@ -284,7 +277,8 @@
|
|||||||
<workItem from="1768547023783" duration="11371000" />
|
<workItem from="1768547023783" duration="11371000" />
|
||||||
<workItem from="1768894030675" duration="83922000" />
|
<workItem from="1768894030675" duration="83922000" />
|
||||||
<workItem from="1769413136483" duration="58000" />
|
<workItem from="1769413136483" duration="58000" />
|
||||||
<workItem from="1769413279223" duration="37300000" />
|
<workItem from="1769413279223" duration="40490000" />
|
||||||
|
<workItem from="1769612160652" duration="12834000" />
|
||||||
</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" />
|
||||||
@@ -574,7 +568,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1769435038236</updated>
|
<updated>1769435038236</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="37" />
|
<task id="LOCAL-00037" summary="feat : finalisation de l'étape 1 "Réception" (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 />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -624,7 +626,6 @@
|
|||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="VcsManagerConfiguration">
|
<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="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" />
|
||||||
@@ -649,7 +650,8 @@
|
|||||||
<MESSAGE value="feat : ajout du bundle Malio ednotif pour l'utilisation des WS" />
|
<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="fix : modification de la conf du bundle ednotif" />
|
||||||
<MESSAGE value="feat : update du CHANGELOG.md" />
|
<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 "Réception" (formulaire)" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="feat : finalisation de l'étape 1 "Réception" (formulaire)" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ Frontend conventions
|
|||||||
- Nuxt SSR disabled; Tailwind used.
|
- Nuxt SSR disabled; Tailwind used.
|
||||||
- Layout in `frontend/layouts/default.vue`: max width `1050px`, header full width.
|
- Layout in `frontend/layouts/default.vue`: max width `1050px`, header full width.
|
||||||
- Tailwind custom color palette is `primary` (e.g. `bg-primary-500`).
|
- 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 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.
|
- 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.
|
- `useApi` uses `useNuxtApp().$i18n` (not `useI18n`) to avoid setup-only constraint in service calls.
|
||||||
@@ -44,3 +45,11 @@ Environment & routing
|
|||||||
Notes
|
Notes
|
||||||
- Do not add a GET that creates resources; use POST + PATCH.
|
- Do not add a GET that creates resources; use POST + PATCH.
|
||||||
- Keep endpoints in plural (API Platform convention).
|
- Keep endpoints in plural (API Platform convention).
|
||||||
|
- 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`.
|
||||||
|
|||||||
@@ -33,14 +33,13 @@
|
|||||||
@click="printReceipt"
|
@click="printReceipt"
|
||||||
>Générer le bon</button>
|
>Générer le bon</button>
|
||||||
</div>
|
</div>
|
||||||
<UiPdfPrinter ref="pdfPrinter" />
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { useWeighing } from '~/composables/useWeighing'
|
import { useWeighing } from '~/composables/useWeighing'
|
||||||
|
import { usePdfPrinter } from '~/composables/usePdfPrinter'
|
||||||
import { useReceptionStore } from '~/stores/reception'
|
import { useReceptionStore } from '~/stores/reception'
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -50,14 +49,9 @@ const props = defineProps<{
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const receptionStore = useReceptionStore()
|
const receptionStore = useReceptionStore()
|
||||||
const { current: storeReception } = storeToRefs(receptionStore)
|
const { current: storeReception } = storeToRefs(receptionStore)
|
||||||
type PdfPrinterHandle = {
|
const { printPdf } = usePdfPrinter()
|
||||||
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 {
|
const {
|
||||||
displayWeight,
|
displayWeight,
|
||||||
displayDsd,
|
|
||||||
title,
|
title,
|
||||||
showLoadingBox,
|
showLoadingBox,
|
||||||
fetchWeight,
|
fetchWeight,
|
||||||
@@ -73,12 +67,12 @@ const showGenerateReceipt = computed(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const printReceipt = async () => {
|
const printReceipt = async () => {
|
||||||
if (!import.meta.client || !receptionStore.current || !pdfPrinter.value) {
|
if (!import.meta.client || !receptionStore.current) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await saveWeight()
|
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.
|
// Laisse le temps a la boite de dialogue d'impression de s'ouvrir.
|
||||||
await new Promise((resolve) => setTimeout(resolve, 600))
|
await new Promise((resolve) => setTimeout(resolve, 600))
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -1,37 +1,26 @@
|
|||||||
import type { Ref } from 'vue'
|
import {useApi} from '~/composables/useApi'
|
||||||
import { useApi } from '~/composables/useApi'
|
|
||||||
|
|
||||||
type PrintFrameRef = Ref<HTMLIFrameElement | null>
|
|
||||||
|
|
||||||
export const usePdfPrinter = () => {
|
export const usePdfPrinter = () => {
|
||||||
const api = useApi()
|
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) {
|
if (!import.meta.client) {
|
||||||
return
|
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 blob = await api.getBlob(url)
|
||||||
const blobUrl = URL.createObjectURL(blob)
|
const blobUrl = URL.createObjectURL(blob)
|
||||||
|
const a = document.createElement('a');
|
||||||
const tryPrint = () => {
|
a.href = url;
|
||||||
frame.contentWindow?.focus()
|
// nom du fichier à changer par les infos du store pinia
|
||||||
frame.contentWindow?.print()
|
a.download = `${currentReception.identificationNumber}_${currentReception.supplier.name}_${currentReception.licensePlate}`;
|
||||||
}
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
frame.onload = () => {
|
a.remove();
|
||||||
tryPrint()
|
window.open(blobUrl, '_blank', 'noopener,noreferrer')
|
||||||
// On libere l'URL blob apres l'impression.
|
setTimeout(() => URL.revokeObjectURL(blobUrl), 60000)
|
||||||
setTimeout(() => URL.revokeObjectURL(blobUrl), 2000)
|
|
||||||
}
|
|
||||||
frame.src = blobUrl
|
|
||||||
setTimeout(tryPrint, 1200)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import type { DriverData } from '~/services/dto/driver-data'
|
|||||||
|
|
||||||
export interface ReceptionData {
|
export interface ReceptionData {
|
||||||
id: number
|
id: number
|
||||||
|
identificationNumber?: string | null
|
||||||
licensePlate: string | null
|
licensePlate: string | null
|
||||||
weights?: WeightEntryData[] | null
|
weights?: WeightEntryData[] | null
|
||||||
receptionDate: string
|
receptionDate: string
|
||||||
|
|||||||
29
migrations/Version20260128000100.php
Normal file
29
migrations/Version20260128000100.php
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,19 +35,19 @@ class Address
|
|||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 120)]
|
#[ORM\Column(length: 120)]
|
||||||
#[Groups(['address:read', 'supplier:read'])]
|
#[Groups(['address:read', 'supplier:read', 'reception:read'])]
|
||||||
private string $label = '';
|
private string $label = '';
|
||||||
|
|
||||||
#[ORM\Column(length: 180)]
|
#[ORM\Column(length: 180)]
|
||||||
#[Groups(['address:read', 'supplier:read'])]
|
#[Groups(['address:read', 'supplier:read', 'reception:read'])]
|
||||||
private string $street = '';
|
private string $street = '';
|
||||||
|
|
||||||
#[ORM\Column(name: 'postal_code', length: 20)]
|
#[ORM\Column(name: 'postal_code', length: 20)]
|
||||||
#[Groups(['address:read', 'supplier:read'])]
|
#[Groups(['address:read', 'supplier:read', 'reception:read'])]
|
||||||
private string $postalCode = '';
|
private string $postalCode = '';
|
||||||
|
|
||||||
#[ORM\Column(length: 120)]
|
#[ORM\Column(length: 120)]
|
||||||
#[Groups(['address:read', 'supplier:read'])]
|
#[Groups(['address:read', 'supplier:read', 'reception:read'])]
|
||||||
private string $city = '';
|
private string $city = '';
|
||||||
|
|
||||||
#[ORM\Column(name: 'country_code', length: 2)]
|
#[ORM\Column(name: 'country_code', length: 2)]
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use App\State\ReceptionWeighingProvider;
|
|||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
|
use Doctrine\ORM\Event\PostPersistEventArgs;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use Symfony\Component\Serializer\Attribute\Context;
|
use Symfony\Component\Serializer\Attribute\Context;
|
||||||
use Symfony\Component\Serializer\Attribute\Groups;
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
@@ -78,6 +79,10 @@ class Reception
|
|||||||
#[Groups(['reception:read', 'reception:write'])]
|
#[Groups(['reception:read', 'reception:write'])]
|
||||||
private ?string $licensePlate = null;
|
private ?string $licensePlate = null;
|
||||||
|
|
||||||
|
#[ORM\Column(length: 20, unique: true, nullable: true)]
|
||||||
|
#[Groups(['reception:read'])]
|
||||||
|
private ?string $identificationNumber = null;
|
||||||
|
|
||||||
#[ORM\Column(options: ['default' => 0])]
|
#[ORM\Column(options: ['default' => 0])]
|
||||||
#[Groups(['reception:read', 'reception:write'])]
|
#[Groups(['reception:read', 'reception:write'])]
|
||||||
private int $currentStep = 0;
|
private int $currentStep = 0;
|
||||||
@@ -155,6 +160,18 @@ class Reception
|
|||||||
return $this->licensePlate;
|
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
|
public function setLicensePlate(?string $licensePlate): self
|
||||||
{
|
{
|
||||||
$this->licensePlate = $licensePlate;
|
$this->licensePlate = $licensePlate;
|
||||||
@@ -321,4 +338,30 @@ class Reception
|
|||||||
$this->receptionDate = new DateTimeImmutable();
|
$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,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
@page { margin: 56px 56px; }
|
@page { margin: 56px 56px; }
|
||||||
|
|
||||||
body{
|
body{
|
||||||
font-family: Arial, sans-serif;
|
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
margin:0;
|
margin:0;
|
||||||
color:#000;
|
color:#000;
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
.red{ color:red; }
|
.red{ color:red; }
|
||||||
|
|
||||||
.company-block{
|
.company-block{
|
||||||
font-size:13px;
|
font-size:14px;
|
||||||
text-align:left;
|
text-align:left;
|
||||||
line-height:1.25;
|
line-height:1.25;
|
||||||
}
|
}
|
||||||
@@ -36,15 +36,30 @@
|
|||||||
text-align:center;
|
text-align:center;
|
||||||
font-size: 18pt;
|
font-size: 18pt;
|
||||||
font-weight: 700;
|
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{
|
th, td{
|
||||||
border:1px solid #333;
|
border:1px solid #333;
|
||||||
padding:4px 6px;
|
padding:4px 6px;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
font-size: 9pt;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
th{ text-align:center; font-weight:700; }
|
th{ text-align:center; font-weight:700; }
|
||||||
|
|
||||||
@@ -52,7 +67,17 @@
|
|||||||
.layout, .layout td{ border:none !important; padding:0; }
|
.layout, .layout td{ border:none !important; padding:0; }
|
||||||
|
|
||||||
/* GRAND TABLEAU : verrouillage dompdf */
|
/* 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 */
|
/* ligne “filler” pour forcer la hauteur comme l'exemple */
|
||||||
.fill td{
|
.fill td{
|
||||||
@@ -60,32 +85,9 @@
|
|||||||
height: 75mm; /* ajuste si besoin */
|
height: 75mm; /* ajuste si besoin */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bloc IDTF comme l’exemple */
|
.signature-title{ font-size:12px; margin-bottom:2mm; }
|
||||||
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-box{ height: 22mm; margin-bottom: 4mm; }
|
.signature-box{ height: 22mm; margin-bottom: 4mm; }
|
||||||
.meta{ font-size: 9pt; line-height: 1.35; }
|
.meta{ font-size: 16px; line-height: 1.35; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -95,33 +97,26 @@
|
|||||||
<table class="layout" style="width:100%;">
|
<table class="layout" style="width:100%;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width:70%; vertical-align:top;">
|
<td style="width:70%; vertical-align:top;">
|
||||||
<!-- table imbriquée : 1 ligne logo, 1 ligne texte (dompdf-friendly) -->
|
|
||||||
<table class="layout" style="width:100%;">
|
<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>
|
<tr>
|
||||||
<td class="company-block" style="padding:0; border:none;">
|
<td class="company-block" style="padding:0; border:none;">
|
||||||
<strong>SA LIOT Châtellerault</strong><br>
|
<strong>SCEA LES NAUDS</strong><br>
|
||||||
Site de <b>Châtellerault</b><br>
|
|
||||||
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 – Fax : 05 49 85 37 82<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 339 505 612
|
RCS Châtellerault B 444 262 455
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td style="width:30%; text-align:left; vertical-align:top;">
|
<td style="width:30%; text-align:right; vertical-align:top; font-size: 14px;">
|
||||||
<div class="box" style="display:inline-block; width:75mm;">
|
<div style="display:inline-block; width:75mm;">
|
||||||
<strong class="red">Nom de l'entreprise</strong><br><br><br>
|
<strong>{{ reception.supplier.name }}</strong><br>
|
||||||
<span class="red">Adresse de l'entreprise</span>
|
<span>{{ reception.address.street }}</span><br>
|
||||||
|
<span>{{ reception.address.postalCode }} {{ reception.address.city }}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -130,18 +125,18 @@
|
|||||||
<div class="title">BON DE RECEPTION</div>
|
<div class="title">BON DE RECEPTION</div>
|
||||||
|
|
||||||
<!-- INFOS (code/date/num) -->
|
<!-- INFOS (code/date/num) -->
|
||||||
<table style="margin-bottom:3mm; width:100%; border-collapse:collapse; table-layout:fixed;">
|
<table class="info-table">
|
||||||
<tr>
|
<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;">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>
|
||||||
<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;">
|
<td style="width:20%; text-align:center; white-space:nowrap;">
|
||||||
{{ reception.receptionDate|date('d/m/Y') }}
|
{{ reception.receptionDate|date('d/m/Y') }}
|
||||||
</td>
|
</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>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -150,20 +145,17 @@
|
|||||||
<table class="bigtable" style="margin-bottom:10px; width:100%; border-collapse:collapse; table-layout:fixed;">
|
<table class="bigtable" style="margin-bottom:10px; width:100%; border-collapse:collapse; table-layout:fixed;">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width:15%; text-align:center;">Code</th>
|
<th style="width:75%; text-align:center;">Désignation</th>
|
||||||
<th style="width:65%; text-align:center;">Désignation</th>
|
<th style="width:25%; text-align:right; white-space:nowrap;">Qté livrée (kg)</th>
|
||||||
<th style="width:20%; text-align:right; white-space:nowrap;">Qté livrée (kg)</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="red" style="width:12%;">M</td>
|
<td style="width:75%;">
|
||||||
|
|
||||||
<td style="width:68%;">
|
|
||||||
<strong class="red">MAÏS sec</strong><br><br>
|
<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 grossWeight = null %}
|
||||||
{% set tareWeight = null %}
|
{% set tareWeight = null %}
|
||||||
|
|
||||||
@@ -179,7 +171,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</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 %}
|
{% if grossWeight and tareWeight %}
|
||||||
{{ grossWeight.weight - tareWeight.weight }}
|
{{ grossWeight.weight - tareWeight.weight }}
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -190,9 +182,8 @@
|
|||||||
|
|
||||||
<!-- filler : garde le grand bloc haut comme sur l'exemple -->
|
<!-- filler : garde le grand bloc haut comme sur l'exemple -->
|
||||||
<tr class="fill">
|
<tr class="fill">
|
||||||
<td style="width:15%;"></td>
|
<td style="width:75%;"></td>
|
||||||
<td style="width:65%;"></td>
|
<td style="width:25%;"></td>
|
||||||
<td style="width:20%;"></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -202,32 +193,16 @@
|
|||||||
<table class="layout">
|
<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 red">
|
<div class="meta">
|
||||||
Transporteur : <strong class="red">Nom du transporteur</strong><br>
|
Transporteur : <strong>{{ reception.carrier.name }}</strong><br>
|
||||||
Mode de livraison : <strong class="red">Fond-mouvant</strong><br>
|
Mode de livraison : <strong>{{ reception.truck.name }}</strong><br>
|
||||||
Immatriculation : <strong class="red">{{ reception.licensePlate }}</strong><br><br>
|
Immatriculation : <strong>{{ reception.licensePlate }}</strong><br><br>
|
||||||
Poids annoncé : <strong class="red">XXXXX kg</strong>
|
|
||||||
</div>
|
</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>
|
||||||
|
|
||||||
<td style="width:40%; vertical-align:top;">
|
<td style="width:40%; vertical-align:top;">
|
||||||
<div class="signature-title">Signature :</div>
|
<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>
|
<div class="box signature-box">Transporteur :</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user