diff --git a/frontend/modules/logistique/components/WeighingBlock.vue b/frontend/modules/logistique/components/WeighingBlock.vue
index 1c93628..d8db47e 100644
--- a/frontend/modules/logistique/components/WeighingBlock.vue
+++ b/frontend/modules/logistique/components/WeighingBlock.vue
@@ -32,9 +32,10 @@
-
-
+ ({ todayIso: () => '2026-06-22' }))
+// `nowIsoDateTime` est importé par le composable : on le stubbe pour un instant déterministe.
+vi.mock('~/shared/utils/date', () => ({ nowIsoDateTime: () => '2026-06-22T08:30:00' }))
const { useWeighingTicketForm } = await import('../useWeighingTicketForm')
describe('useWeighingTicketForm', () => {
- it('initialise les 2 blocs à la date du jour (RG-5.07), sans poids ni DSD', () => {
+ it('initialise les 2 blocs à la date/heure courante (RG-5.07), sans poids ni DSD', () => {
const form = useWeighingTicketForm()
- expect(form.empty.date).toBe('2026-06-22')
- expect(form.full.date).toBe('2026-06-22')
+ expect(form.empty.date).toBe('2026-06-22T08:30:00')
+ expect(form.full.date).toBe('2026-06-22T08:30:00')
expect(form.empty.weight).toBeNull()
expect(form.empty.dsd).toBeNull()
expect(form.counterpartyType.value).toBeNull()
@@ -25,8 +25,8 @@ describe('useWeighingTicketForm', () => {
expect(payload).not.toHaveProperty('counterpartyType')
expect(payload).not.toHaveProperty('immatriculation')
expect(payload).not.toHaveProperty('emptyWeight')
- // Les non-null restent : date du jour + booléen Tout format.
- expect(payload.emptyDate).toBe('2026-06-22')
+ // Les non-null restent : date/heure courante + booléen Tout format.
+ expect(payload.emptyDate).toBe('2026-06-22T08:30:00')
expect(payload.plateFreeFormat).toBe(false)
})
@@ -96,9 +96,13 @@ describe('useWeighingTicketForm', () => {
})
// ── Application d'une lecture de pesée ────────────────────────────────────
- it('applyReading remplit poids / DSD / mode du bloc visé', () => {
+ it('applyReading remplit poids / DSD / mode et ré-horodate le bloc à l\'instant de la pesée', () => {
const form = useWeighingTicketForm()
+ // Date périmée (ouverture du formulaire bien avant la pesée).
+ form.empty.date = '2020-01-01T00:00:00'
form.applyReading(form.empty, { weight: 7150, dsd: 1, mode: 'AUTO' })
+ // La pesée validée ré-horodate le bloc à maintenant (stub 2026-06-22T08:30:00).
+ expect(form.empty.date).toBe('2026-06-22T08:30:00')
expect(form.empty.weight).toBe(7150)
expect(form.empty.dsd).toBe(1)
expect(form.empty.mode).toBe('AUTO')
@@ -129,7 +133,7 @@ describe('useWeighingTicketForm', () => {
})
// ── Pré-remplissage (écran Modification, ERP-190) ─────────────────────────
- it('hydrate pré-remplit l\'état depuis le détail (dates ISO ramenées à YYYY-MM-DD)', () => {
+ it('hydrate pré-remplit l\'état depuis le détail (datetime ISO ramené en local, heure conservée)', () => {
const form = useWeighingTicketForm()
form.hydrate({
id: 9,
@@ -152,9 +156,9 @@ describe('useWeighingTicketForm', () => {
expect(form.counterpartyField.value).toBe('client')
expect(form.clientIri.value).toBe('/api/clients/629')
expect(form.immatriculation.value).toBe('AB-123-CD')
- // Date datetime back -> date seule pour MalioDate.
- expect(form.empty.date).toBe('2026-06-17')
- expect(form.full.date).toBe('2026-06-17')
+ // Datetime back (avec fuseau) -> local sans fuseau, heure conservée pour MalioDateTime.
+ expect(form.empty.date).toBe('2026-06-17T09:00:00')
+ expect(form.full.date).toBe('2026-06-17T09:12:00')
expect(form.empty.weight).toBe(7150)
expect(form.full.weight).toBe(14300)
})
@@ -165,8 +169,8 @@ describe('useWeighingTicketForm', () => {
expect(form.otherLabel.value).toBe('Reprise')
expect(form.supplierIri.value).toBeNull()
expect(form.plateFreeFormat.value).toBe(false)
- // Pas de date back -> repli sur le jour (stub 2026-06-22).
- expect(form.empty.date).toBe('2026-06-22')
+ // Pas de date back -> repli sur l'instant courant (stub 2026-06-22T08:30:00).
+ expect(form.empty.date).toBe('2026-06-22T08:30:00')
expect(form.empty.weight).toBeNull()
})
diff --git a/frontend/modules/logistique/composables/useWeighingTicketForm.ts b/frontend/modules/logistique/composables/useWeighingTicketForm.ts
index ffa9a38..c3a0563 100644
--- a/frontend/modules/logistique/composables/useWeighingTicketForm.ts
+++ b/frontend/modules/logistique/composables/useWeighingTicketForm.ts
@@ -1,5 +1,5 @@
import { computed, reactive, ref } from 'vue'
-import { todayIso } from '~/shared/utils/date'
+import { nowIsoDateTime } from '~/shared/utils/date'
import type { WeighbridgeMode } from '~/modules/logistique/composables/useWeighbridge'
/**
@@ -27,7 +27,7 @@ export type CounterpartyType = 'CLIENT' | 'FOURNISSEUR' | 'AUTRE'
/** Saisie d'une pesée (bloc vide OU bloc plein). */
export interface WeighingBlockState {
- /** Date de la pesée (ISO `YYYY-MM-DD`) — jour par défaut (RG-5.07). */
+ /** Date/heure de la pesée (ISO local `YYYY-MM-DDTHH:mm:ss`) — date du jour + heure courante par défaut (RG-5.07). */
date: string | null
/** Poids en kg — readonly, rempli par la pesée (bascule ou manuelle). */
weight: number | null
@@ -60,9 +60,14 @@ export interface WeighingTicketHydration {
fullManualNumber?: string | null
}
-/** Extrait la partie date `YYYY-MM-DD` d'une chaîne ISO (datetime back) — null si absente. */
-function isoDateOnly(value: string | null | undefined): string | null {
- return value ? value.slice(0, 10) : null
+/**
+ * Ramène une chaîne ISO datetime du back (`2026-06-17T09:00:00+02:00`) au format
+ * local `YYYY-MM-DDTHH:mm:ss` attendu par MalioDateTime (secondes, sans fuseau) :
+ * on garde les 19 premiers caractères (date + heure), on retire l'offset. Null si
+ * absente.
+ */
+function toLocalIsoDateTime(value: string | null | undefined): string | null {
+ return value ? value.slice(0, 19) : null
}
/**
@@ -78,10 +83,10 @@ function compact(payload: Record): Record {
return Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== null))
}
-/** Crée l'état initial d'un bloc de pesée (date = aujourd'hui, RG-5.07). */
-function emptyBlock(today: string): WeighingBlockState {
+/** Crée l'état initial d'un bloc de pesée (date/heure = maintenant, RG-5.07). */
+function emptyBlock(now: string): WeighingBlockState {
return {
- date: today,
+ date: now,
weight: null,
dsd: null,
mode: null,
@@ -90,7 +95,7 @@ function emptyBlock(today: string): WeighingBlockState {
}
export function useWeighingTicketForm() {
- const today = todayIso()
+ const now = nowIsoDateTime()
// ── Contrepartie (RG-5.03) ───────────────────────────────────────────────
const counterpartyType = ref(null)
@@ -116,8 +121,8 @@ export function useWeighingTicketForm() {
const plateFreeFormat = ref(false)
// ── Les deux pesées ───────────────────────────────────────────────────────
- const empty = reactive(emptyBlock(today))
- const full = reactive(emptyBlock(today))
+ const empty = reactive(emptyBlock(now))
+ const full = reactive(emptyBlock(now))
// Id du ticket créé (POST du bloc vide) — pilote le PATCH du bloc plein.
const ticketId = ref(null)
@@ -150,11 +155,17 @@ export function useWeighingTicketForm() {
return missing
}
- /** Applique une lecture de pesée (bascule/manuelle) à un bloc. */
+ /**
+ * Applique une lecture de pesée (bascule/manuelle) à un bloc. La pesée étant
+ * effectuée À CET INSTANT, on (ré)horodate le bloc à maintenant : la date/heure
+ * du ticket reflète le moment réel de la pesée validée, pas l'ouverture du
+ * formulaire (RG-5.07).
+ */
function applyReading(
block: WeighingBlockState,
reading: { weight: number, dsd: number, mode: WeighbridgeMode, manualNumber?: string },
): void {
+ block.date = nowIsoDateTime()
block.weight = reading.weight
block.dsd = reading.dsd
block.mode = reading.mode
@@ -195,7 +206,8 @@ export function useWeighingTicketForm() {
* Pré-remplit le formulaire à partir du détail d'un ticket existant (écran
* Modification, ERP-190). Le numéro et le site sont immuables (RG-5.09) →
* non repris dans l'état éditable (affichés en lecture seule par l'écran).
- * Les dates ISO du back (datetime) sont ramenées à `YYYY-MM-DD` pour MalioDate.
+ * Les dates ISO du back (datetime + fuseau) sont ramenées au format local
+ * `YYYY-MM-DDTHH:mm:ss` attendu par MalioDateTime (heure conservée).
*/
function hydrate(detail: WeighingTicketHydration): void {
ticketId.value = detail.id
@@ -206,13 +218,13 @@ export function useWeighingTicketForm() {
immatriculation.value = detail.immatriculation ?? null
plateFreeFormat.value = detail.plateFreeFormat ?? false
- empty.date = isoDateOnly(detail.emptyDate) ?? today
+ empty.date = toLocalIsoDateTime(detail.emptyDate) ?? now
empty.weight = detail.emptyWeight ?? null
empty.dsd = detail.emptyDsd ?? null
empty.mode = detail.emptyMode ?? null
empty.manualNumber = detail.emptyManualNumber ?? null
- full.date = isoDateOnly(detail.fullDate) ?? today
+ full.date = toLocalIsoDateTime(detail.fullDate) ?? now
full.weight = detail.fullWeight ?? null
full.dsd = detail.fullDsd ?? null
full.mode = detail.fullMode ?? null
diff --git a/frontend/modules/logistique/pages/__tests__/weighingTicketEdit.spec.ts b/frontend/modules/logistique/pages/__tests__/weighingTicketEdit.spec.ts
index 7a60fc9..c3b3b03 100644
--- a/frontend/modules/logistique/pages/__tests__/weighingTicketEdit.spec.ts
+++ b/frontend/modules/logistique/pages/__tests__/weighingTicketEdit.spec.ts
@@ -64,7 +64,7 @@ const stubs = {
MalioInputText: InputStub,
MalioInputNumber: InputStub,
MalioSelect: InputStub,
- MalioDate: InputStub,
+ MalioDateTime: InputStub,
MalioCheckbox: InputStub,
MalioModal: ModalStub,
WeighingBlock: BlockStub,
diff --git a/frontend/shared/utils/date.ts b/frontend/shared/utils/date.ts
index 7123ffa..d75cb73 100644
--- a/frontend/shared/utils/date.ts
+++ b/frontend/shared/utils/date.ts
@@ -15,3 +15,18 @@ export function todayIso(now: Date = new Date()): string {
const day = String(now.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
}
+
+/**
+ * Date-heure courante au format ISO LOCAL `YYYY-MM-DDTHH:mm:ss` (sans fuseau).
+ *
+ * C'est le format attendu par `MalioDateTime` (secondes incluses, pas d'offset
+ * horaire). Comme `todayIso`, on lit les composantes LOCALES (jamais
+ * `toISOString()`/UTC) pour ne pas décaler l'heure réelle. Paramètre `now`
+ * injectable pour les tests.
+ */
+export function nowIsoDateTime(now: Date = new Date()): string {
+ const hours = String(now.getHours()).padStart(2, '0')
+ const minutes = String(now.getMinutes()).padStart(2, '0')
+ const seconds = String(now.getSeconds()).padStart(2, '0')
+ return `${todayIso(now)}T${hours}:${minutes}:${seconds}`
+}