La semaine type
Le cycle quotidien décrit ce que fait le moteur heure par heure. Cette page prend l'échelle du dessus — la semaine — parce que c'est là que naissent la plupart des « je ne comprends plus mon stock » : deux calendriers tournent en parallèle, le physique (collectes, tournées, comptages) et celui de l'application (créneaux d'envoi, dates de réception prévues, scellés automatiques), et ils ne coïncident pas exactement.
La seule idée à retenir. L'application date le stock à l'heure prévue (envoi + délai de réception), pas à l'heure réelle (la collecte du matin). Tout le reste — le décalage, les fenêtres à risque, quand compter, quoi régler — découle de cet écart.
La semaine décrite ici est la configuration de production réelle d'un marchand type (épicerie de producteurs, ~100 fournisseurs), mais la mécanique — créneau d'envoi + délai de réception
- grâce d'auto-validation — est la même pour tout tenant.
La règle, tout de suite
Comptez ce qui est physiquement sur l'étagère, au moment où vous comptez — l'appli aligne le reste. Une seule chose à éviter : les deux courtes fenêtres où la marchandise est déjà sur l'étal mais pas encore « reçue » côté application. Un comptage qui y tombe est doublé : l'appli rajoute la marchandise par-dessus au scellé suivant.
| Fenêtre | Statut | Pourquoi |
|---|---|---|
| Mardi, collecte → 12:15 (~3 h) | ⚠️ à risque | Vague 1 sur l'étal mais « pas encore reçue » pour l'appli |
| Jeudi, collecte → 22:15 (~13 h) | ⚠️ à risque | Vague 2 sur l'étal mais « pas encore reçue » pour l'appli |
| Tout le reste de la semaine | ✅ sûr | Y compris le vendredi après-midi |
Et le raccourci qui rend ces fenêtres sans objet : confirmer la réception (ou valider la tournée) au moment physique, dans l'UI. Le geste manuel date l'arrivée à l'instant vrai et court-circuite toute supposition — le scellé automatique n'est qu'un filet. Le reste de la page explique pourquoi ces fenêtres existent et où elles tombent.
Les deux vagues fournisseurs
Chaque fournisseur porte des créneaux d'envoi (sendSchedules), un délai de commande
(deliveryDays, pilote l'affectation des créneaux — 2 par défaut) et un délai de
réception (receptionLeadDays, pilote la date d'arrivée attendue — 1 jour pour tous les
fournisseurs amisferme). En production, l'essentiel du volume tient en deux vagues :
| Vague | Créneau d'envoi | Fournisseurs | Réception attendue (délai de réception 1 j) | Scellé auto si non confirmée |
|---|---|---|---|---|
| Vague 1 | lundi 12:15 | ~95 | mardi 12:15 | mercredi ~02:01 |
| Vague 2 | mercredi 22:15 | ~87 | jeudi 22:15 | vendredi ~12:01 |
La date de réception attendue est mécanique : expectedDeliveryDate = envoi + (receptionLeadDays ?? deliveryDays) (voir
Commandes fournisseur). C'est cette date — pas la
date d'arrivée réelle — que lit l'auto-validation pour supposer la réception, et que lit
la loi de l'ancre pour décider si un comptage a déjà absorbé
la marchandise.
La semaine, sur les deux calendriers
| Jour | Côté marchand (physique) | Côté application (dates prévues) |
|---|---|---|
| Lundi | Préparation ; l'appli envoie la vague 1 à 12:15 | Emails vague 1 partis ; brouillons figés |
| Mardi | Collecte vague 1 (matin) — l'étal est réassorti | 12:15 : date de réception attendue vague 1 atteinte (envoi + 1 j) |
| Mercredi | Tournées client ; l'appli envoie la vague 2 à 22:15 | ~02:01 : réception vague 1 supposée (grâce 12 h écoulée, balayage 2-horaire) ; emails vague 2 partis |
| Jeudi | Collecte vague 2 (matin) ; tournées client | 22:15 : date de réception attendue vague 2 atteinte |
| Vendredi | Tournées client | ~12:01 : réception vague 2 supposée |
| Samedi | Tournées client | Scellés des tournées de la veille seulement |
| Dimanche | — | Régime de fond seulement (syncs, files) |
L'animation ci-dessous rejoue la semaine avec des quantités illustratives. Suivez les deux compteurs : l'étal réel (trait plein vert) prend la marchandise à la collecte ; le stock vu par l'appli (pointillé violet) ne la prend qu'au scellé automatique, quelques heures plus tard (délai de réception 1 j + grâce 12 h). La bande ambrée entre les deux courbes matérialise l'écart, et les zones rouges sont les fenêtres où un comptage physique serait doublé. Cliquez n'importe où sur la frise pour tester un moment de comptage : le verdict vous dit s'il serait sûr ou doublé, et de combien.
Les heures de scellé (02:01, 12:01) sont des heures serveur (UTC) — comme sur la
page cycle quotidien. Les créneaux d'envoi (12:15,
22:15) sont ceux affichés dans la configuration des fournisseurs.
Le décalage résiduel : collecté le matin, « reçu » à l'heure prévue
Même avec le délai de réception à 1 jour, un petit décalage subsiste : la marchandise est physiquement sur l'étal le matin de la collecte (mardi ~09:00, jeudi ~09:00), mais l'application ne la date qu'à l'heure prévue (mardi 12:15, jeudi 22:15) et ne la scelle qu'après la grâce de 12 h (mercredi ~02:01, vendredi ~12:01). D'où deux conséquences, désormais bornées à quelques heures :
- Le stock apparaît avec quelques heures de retard dans l'appli — sauf confirmation manuelle de la réception, qui date l'arrivée à l'instant vrai.
- Un comptage fait dans la fenêtre à risque est doublé. La loi de l'ancre n'absorbe que les événements dont la date prévue est antérieure au comptage. Un comptage fait entre la collecte réelle et l'heure prévue (mardi 09:00 → 12:15, ou jeudi 09:00 → 22:15) contient déjà la marchandise… que l'appli croit « pas encore reçue » et rajoute par-dessus au scellé suivant : +N unités fantômes sur un comptage pourtant juste.
Pourquoi ces fenêtres sont désormais courtes. Jusqu'au 2026-07-16, le délai de
réception n'existait pas comme champ propre : la réception était datée envoi + deliveryDays
(soit + 2 j), donc vague 1 mercredi 12:15 et vague 2 vendredi 22:15, scellées jeudi
02:01 / samedi 12:01 — un à deux jours après l'arrivée réelle. Les fenêtres à risque
duraient alors 26 h et 36 h : c'est la signature du ticket #2936, « j'ai compté vendredi
après-midi, et samedi mon stock avait grossi tout seul ». Le comptage tombait avant la date
prévue (vendredi 22:15) d'une marchandise collectée le jeudi ; ni le comptage ni la réception
supposée n'étaient faux isolément — la date de réception attendue mentait d'un jour. Depuis
que receptionLeadDays est un champ dédié (livré par la PR #848, backfillé à 1 j pour tous
les fournisseurs amisferme), les fenêtres tombent à ~3 h et ~13 h, et un comptage du
vendredi après-midi est sûr.
Quand compter le stock
La règle ci-dessus suffit pour les réceptions fournisseurs. Une seule précision, côté
commandes client du jour : leur deliveryDate porte une heure. Une commande dont
l'heure de livraison est déjà passée est considérée comme partie (pas de second décrément —
ne comptez pas ces produits) ; une commande prévue plus tard dans la journée sera décomptée
à son heure (comptez ces produits s'ils sont encore là). Le plus simple : compter le soir
après les tournées, ou le matin avant les départs.
Ajuster la semaine
Trois leviers, du plus efficace au plus ponctuel :
- Régler le « Délai de réception » du fournisseur (fiche fournisseur, carte
Paramètres de livraison — champ
receptionLeadDays, livré le 2026-07-16 et backfillé à 1 jour pour tous les fournisseurs amisferme). Vide = hérite du délai de commande. ⚠️ Ne jamais réduiredeliveryDaysà la place : ce champ sert aussi à choisir quel email couvre quelle commande client (fenêtre d'envoi =livraison client − délai − 1 j de marge). Avec les deux créneaux hebdomadaires, la valeur 2 est précisément ce qui met les livraisons de mercredi/jeudi sur l'email du lundi et celles de vendredi/samedi sur celui du mercredi — la passer à 1 enverrait les commandes du jeudi sur le mauvais email et laisserait celles du samedi sans créneau (voir la règle épinglée parreception-lead-days.test.ts). - Confirmer la réception à la collecte. Le geste UI au moment physique est le chemin primaire — il date la réception à l'instant vrai, et l'auto-validation n'a alors rien à supposer. Le scellé automatique n'est qu'un filet de sécurité, pas le mode nominal.
- Annuler une réception supposée à tort. Sur la page Stock
(
/manage/ecommerce/inventory/stock-ledger), le panneau « Réceptions supposées » liste tout ce que le filet a scellé, avec un bouton « Annuler la réception » (retire le stock, restaure les projections, rouvre la commande — et la commande ne sera pas re-scellée par le balayage suivant). Voir Écrans du back-office.
La grâce globale (AUTO_VALIDATE_GRACE_HOURS, 12 h) est volontairement hors de cette
liste : la raccourcir rapproche les scellés des dates prévues mais ré-ouvre le risque de
sceller des livraisons pas encore advenues — c'est le délai par fournisseur qu'il faut
régler, pas la grâce. Voir Flags & configuration.
Et les commandes client, dans tout ça ?
Symétriquement aux réceptions, les livraisons client sont supposées effectuées à
deliveryDate + 12 h : une tournée du vendredi 11:00 non confirmée est scellée dans la
nuit de vendredi à samedi (balayage de ~00:01), avec le décrément de stock correspondant.
Là aussi le scellé UI (« Valider la tournée du jour ») au retour de tournée est le geste
primaire ; le filet ne fait que rattraper l'oubli, environ 12 à 14 h après l'heure prévue.
En résumé
- Deux calendriers : le physique (collectes mardi/jeudi, tournées mercredi→samedi) et le prévu (envoi + délai, scellé + grâce). Ils divergent d'autant plus que la date de réception attendue s'écarte de l'arrivée réelle.
- Un comptage est sûr après l'heure de réception attendue de la dernière vague, ou avant sa collecte — jamais entre les deux. Aujourd'hui, les fenêtres à éviter font ~3 h (mardi) et ~13 h (jeudi).
- Le geste manuel (confirmer réception / valider tournée) au moment physique court-circuite toutes les suppositions : c'est toujours la meilleure option.