## Choix de la table du PK pour chaque palette arrivant au PS > **Version 1.0** — 27 avril 2026 > > Ce document décrit l'algorithme exécuté par le WMS lorsqu'une palette source arrive au **PS (poste de sortie)** et que celui-ci demande au WMS **sur quelle table du PK** la poser. > > **Ce qui est hors périmètre** : l'ordre de sortie des palettes du TK, le tri par espèce/TC/quantité/poids, l'écriture des `Line.CstAtt` et `OS.CstAtt`. Tout cela est géré par un autre process (déclenché sur `TaskCreatedEvent` / `OutboundOrderReleasedEvent`). Ici, les palettes arrivent **déjà triées** au PS. --- ## 0. Rappel du contexte physique Chaque PK dispose de **3 tables** soumises à une contrainte d'adjacence stricte : ``` TABLE_GAUCHE ←→ TABLE_CENTRE ←→ TABLE_DROITE ✅ adjacentes ✅ adjacentes TABLE_GAUCHE ←————————————————→ TABLE_DROITE ❌ INTERDIT ``` **Règles de mouvement :** - Le stock est **toujours sur une palette**, jamais directement posé sur la table. - L'opérateur peut déplacer du **stock** (sacs, colis) d'une palette à une autre **uniquement entre deux tables adjacentes**. - Il est **interdit de déplacer une palette d'une table à une autre**. Seul l'AGV peut déplacer une palette (arrivée, évacuation, recentrage). - **TABLE_CENTRE** est le pivot : c'est la seule table adjacente aux deux autres. --- ## 1. Déclenchement L'algorithme est appelé **à chaque fois qu'une palette arrive physiquement au PS**. Le PS interroge le WMS qui retourne une réponse parmi : - **TABLE_GAUCHE**, **TABLE_CENTRE** ou **TABLE_DROITE** → la palette est envoyée sur cette table. - **Buffer ESx** → aucune table n'est disponible, la palette est redirigée vers un emplacement buffer. - **Attente** → ni table ni buffer disponible, la palette reste au PS. --- ## 2. Entrées de l'algorithme | Donnée | Source | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | | **Tâche courante** : palette source, article, quantité, type de picking (NÉGATIF / DIRECT), `Line.CstAtt` (numéro de séquence) | Tâche associée à la palette | | **État des 3 tables du PK cible** : VIDE, PALETTE_FILLE_ACTIVE, PALETTE_FILLE_EN_ATTENTE, PALETTE_SOURCE_EN_PICKING, PALETTE_SOURCE_EN_ATTENTE, EN_ATTENTE_EVACUATION | État temps réel du PK | | **Tâches suivantes de l'OS** (triées par `Line.CstAtt` croissant) | OS en base | | **Buffers ES disponibles** : parmi ES1–ES16, ceux qui ne sont ni occupés ni ciblés par une tâche en cours | État temps réel des buffers | | **Nombre de buffers déjà affectés à ce PK** vs `MAX_PRELOAD_PAR_PK` (défaut : 3) | Compteur par PK | | **Traitement commercial (TC)** de l'article et flag `CONTROLE_TRAITEMENT_COMMERCIAL` | Fiche article / paramètre WMS | --- ## 3. Décision de niveau 1 : table du PK ou buffer ? ``` FONCTION décider_destination(palette, PK) : table_cible ← choisir_table(palette, PK) // voir §4 SI table_cible ≠ NULL : RETOURNER table_cible // Aucune table disponible → tenter un buffer SI nb_buffers_affectés(PK) < MAX_PRELOAD_PAR_PK : buffer ← premier ES libre (pas de palette, pas de tâche en vol) SI buffer existe : RETOURNER buffer // Ni table ni buffer disponible RETOURNER ATTENTE // La palette reste au PS, le WMS réessaiera // dès qu'une place se libère (table ou buffer) ``` --- ## 4. Décision de niveau 2 : choix de la table Le choix dépend du **type de picking** de la tâche associée à la palette. ### 4.1. Cas PICKING_NÉGATIF En picking négatif, la palette source arrive sur une table, l'opérateur retire l'excédent sur une palette posée sur une **table adjacente**, puis échange d'étiquettes : la palette source devient la palette fille. La palette a besoin de **2 tables** : une pour elle, une adjacente libre pour l'excédent. ``` FONCTION choisir_table_picking_négatif(PK) : // ─── Priorité 1 : centre + un côté libre ─── // La palette source va au centre (pivot), l'excédent ira sur un côté. // Maximise la flexibilité : le centre est adjacent aux deux côtés. SI TABLE_CENTRE == VIDE ET TABLE_GAUCHE == VIDE : RETOURNER TABLE_CENTRE SI TABLE_CENTRE == VIDE ET TABLE_DROITE == VIDE : RETOURNER TABLE_CENTRE // ─── Priorité 2 : côté + centre libre ─── // Moins optimal (la palette source n'est pas au pivot) // mais fonctionnel si le centre est libre pour l'excédent. SI TABLE_GAUCHE == VIDE ET TABLE_CENTRE == VIDE : RETOURNER TABLE_GAUCHE SI TABLE_DROITE == VIDE ET TABLE_CENTRE == VIDE : RETOURNER TABLE_DROITE // ─── Priorité 3 : centre libre + un côté libérable ─── SI TABLE_CENTRE == VIDE ET TABLE_GAUCHE est libérable : évacuer(TABLE_GAUCHE) RETOURNER TABLE_CENTRE SI TABLE_CENTRE == VIDE ET TABLE_DROITE est libérable : évacuer(TABLE_DROITE) RETOURNER TABLE_CENTRE // ─── Priorité 4 : un côté libre + centre libérable ─── SI TABLE_GAUCHE == VIDE ET TABLE_CENTRE est libérable : évacuer(TABLE_CENTRE) RETOURNER TABLE_GAUCHE SI TABLE_DROITE == VIDE ET TABLE_CENTRE est libérable : évacuer(TABLE_CENTRE) RETOURNER TABLE_DROITE // ─── Dernier recours : évacuation forcée ─── évacuer_table_prioritaire(PK) // cf. §6 RETOURNER choisir_table_picking_négatif(PK) // rappel récursif ``` ### 4.2. Cas PICKING_DIRECT En picking direct, la palette source doit être posée sur une table **adjacente à la palette fille active**. Le choix se fait donc en fonction de l'emplacement de la palette fille. ``` FONCTION choisir_table_picking_direct(tâche, PK) : // ─── Étape 1 : localiser la palette fille compatible ─── table_fille ← localiser_palette_fille(tâche, PK) // cf. §4.3 SI table_fille == NULL : // Pas encore de palette fille → il faudra en créer une. // On anticipe son emplacement pour choisir la table source. table_fille ← choisir_table_pour_nouvelle_palette_fille(PK) // cf. §4.4 // ─── Étape 2 : choisir une table adjacente pour la palette source ─── tables_adj ← tables_adjacentes(table_fille) // TABLE_GAUCHE → [TABLE_CENTRE] // TABLE_CENTRE → [TABLE_GAUCHE, TABLE_DROITE] // TABLE_DROITE → [TABLE_CENTRE] // Prio A : la palette source est déjà sur une adjacente // (cas multi-tâches pour le même OS depuis la même palette) POUR chaque t DANS tables_adj : SI t contient tâche.PALETTE_SOURCE : RETOURNER t // Prio B : adjacente VIDE // Si 2 adjacentes libres (PF au centre) → appliquer ping-pong (cf. §5) adjacentes_vides ← [t POUR t DANS tables_adj SI t == VIDE] SI len(adjacentes_vides) == 2 : RETOURNER choisir_côté_ping_pong(PK) SI len(adjacentes_vides) == 1 : RETOURNER adjacentes_vides[0] // Prio C : adjacente en cours d'évacuation (AGV en route, bientôt libre) POUR chaque t DANS tables_adj : SI t == EN_ATTENTE_EVACUATION : RETOURNER t // Prio D : forcer l'évacuation d'une adjacente t_à_libérer ← choisir_table_à_évacuer(tables_adj) // cf. §6 évacuer(t_à_libérer) RETOURNER t_à_libérer ``` ### 4.3. Localisation de la palette fille compatible ``` FONCTION localiser_palette_fille(tâche, PK) : // Chercher d'abord parmi les PF actives POUR chaque table DANS [TABLE_GAUCHE, TABLE_CENTRE, TABLE_DROITE] : SI table.état == PALETTE_FILLE_ACTIVE : SI CONTROLE_TRAITEMENT_COMMERCIAL == false : RETOURNER table SINON SI table.palette.TC == tâche.article.TC : RETOURNER table // Puis parmi les PF en attente (sera réactivée par l'exécution) POUR chaque table DANS [TABLE_GAUCHE, TABLE_CENTRE, TABLE_DROITE] : SI table.état == PALETTE_FILLE_EN_ATTENTE : SI CONTROLE_TRAITEMENT_COMMERCIAL == false OU table.palette.TC == tâche.article.TC : RETOURNER table RETOURNER NULL // aucune palette fille compatible ``` ### 4.4. Choix de la table pour une nouvelle palette fille ``` FONCTION choisir_table_pour_nouvelle_palette_fille(PK) : // TABLE_CENTRE = pivot, adjacente aux 2 côtés → maximise la flexibilité SI TABLE_CENTRE == VIDE : RETOURNER TABLE_CENTRE SI TABLE_GAUCHE == VIDE : RETOURNER TABLE_GAUCHE SI TABLE_DROITE == VIDE : RETOURNER TABLE_DROITE // Aucune table vide → forcer une évacuation évacuer_table_prioritaire(PK) // cf. §6 RETOURNER choisir_table_pour_nouvelle_palette_fille(PK) // rappel récursif ``` --- ## 5. Optimisation ping-pong Le ping-pong est l'optimisation principale pour le débit. Il n'est possible que lorsque la **palette fille est au centre** : les palettes sources alternent alors entre TABLE_GAUCHE et TABLE_DROITE, de sorte que la palette suivante est déjà en place quand l'opérateur termine. ``` FONCTION choisir_côté_ping_pong(PK) : // Identifier le côté actuellement occupé par une palette source SI TABLE_GAUCHE.état ∈ {PALETTE_SOURCE_EN_PICKING, PALETTE_SOURCE_EN_ATTENTE} : RETOURNER TABLE_DROITE // envoyer du côté opposé SI TABLE_DROITE.état ∈ {PALETTE_SOURCE_EN_PICKING, PALETTE_SOURCE_EN_ATTENTE} : RETOURNER TABLE_GAUCHE // envoyer du côté opposé // Aucun côté occupé → choix arbitraire RETOURNER TABLE_GAUCHE ``` **Recentrage** : si la palette fille est sur un côté (issue d'un picking négatif), le ping-pong est impossible. Si le nombre de PICKING_DIRECT restants ≥ `SEUIL_RECENTRAGE_PF` (défaut : 3), le WMS peut déclencher un mouvement AGV pour recentrer la PF sur TABLE_CENTRE. --- ## 6. Évacuation des tables — Priorité Lorsque aucune table n'est libre et qu'il faut en libérer une : ``` FONCTION évacuer_table_prioritaire(PK) : // 1. Palette vide → retrait manuel instantané (pas d'AGV) POUR chaque table : SI table contient palette VIDE : Opérateur retire manuellement → table = VIDE RETOURNER table // 2. Table déjà en attente d'évacuation → AGV en route, attendre POUR chaque table : SI table.état == EN_ATTENTE_EVACUATION : Attendre fin du mouvement AGV RETOURNER table // 3. Palette source en attente, non réutilisée par la prochaine tâche POUR chaque table : SI table.état == PALETTE_SOURCE_EN_ATTENTE ET table.palette ∉ prochaines_tâches_de_l_OS : Commander AGV : table → buffer ES ou ASRS RETOURNER table // 4. Palette fille en attente → évacuer vers image de quai POUR chaque table : SI table.état == PALETTE_FILLE_EN_ATTENTE : Commander AGV : table → image de quai RETOURNER table // 5. Palette source en attente (même si réutilisée) → buffer ES POUR chaque table : SI table.état == PALETTE_SOURCE_EN_ATTENTE : Commander AGV : table → buffer ES RETOURNER table ``` --- ## 7. Gestion des sorties buffer → PK Quand une table se libère au PK, le WMS choisit parmi les palettes en buffer affectées à ce PK. ``` FONCTION choisir_palette_buffer_vers_PK(PK, table_libérée) : palettes_en_buffer ← toutes les palettes en ES affectées à ce PK SI palettes_en_buffer est vide : RETOURNER NULL // rien en buffer // Trier par Line.CstAtt croissant (plus petit = plus prioritaire) palettes_triées ← trier(palettes_en_buffer, par Line.CstAtt ASC) // Prendre la palette avec la plus petite séquence // qui est compatible avec la table libérée POUR chaque palette DANS palettes_triées : SI compatible(palette, table_libérée, PK) : Commander AGV : buffer → table_libérée RETOURNER palette RETOURNER NULL // attendre qu'une table compatible se libère ``` **Règle critique** : l'ordre de sortie des buffers est dicté par `Line.CstAtt`, **pas** par l'ordre d'arrivée physique en buffer. --- ## 8. Palettes multi-commandes Une palette source peut être assignée à plusieurs OS. ``` Après picking de la commande en cours : SI palette a encore des tâches pour d'autres OS : → Marquer palette = MULTI_COMMANDE → Commander AGV : table → buffer ES libre → La palette reste en buffer jusqu'au lancement de l'OS suivant Calcul de capacité buffer : Une palette multi-commande au PK ou en mouvement compte comme une place buffer occupée dans le calcul de MAX_PRELOAD_PAR_PK. ``` --- ## 9. Priorité d'accès aux buffers entre PK Lorsque les buffers ES sont saturés : ``` Priorité définie par la séquence du mode "Picking" dans MODES_PKxx (LIM-69). Séquence 1 = priorité la plus haute. Exemple : PK01 : PICKING;1 → priorité haute PK03 : PICKING;2 → priorité moyenne PK05 : PICKING;3 → priorité basse Quand un buffer se libère : → Affecté en priorité au PK avec la séquence la plus basse parmi ceux qui ont des palettes en attente. ``` --- ## 10. Paramètres WMS utilisés |Paramètre|Description|Défaut| |---|---|---| |`MAX_PRELOAD_PAR_PK`|Nombre max de palettes pré-chargées en buffer ES par PK|3| |`SEUIL_RECENTRAGE_PF`|Nombre min de PICKING_DIRECT restants pour recentrer la PF au centre|3| |`CONTROLE_TRAITEMENT_COMMERCIAL`|Séparer ou non les articles par TC sur les palettes filles|`true`| |`MODES_PKxx`|Modes autorisés + priorité par PK (LIM-69)|—| |`PK_BIGBAG`|Autorise ou non les big-bags par PK (LIM-70)|—| --- ## 11. Diagramme de flux synthétique ``` Palette arrive au PS (poste de sortie) Le PS demande au WMS : "sur quelle table ?" │ ▼ ┌─────────────────────┐ │ Type de picking ? │ └───┬────────────┬────┘ │ │ NÉGATIF DIRECT │ │ ▼ ▼ ┌──────────────┐ ┌──────────────────────────┐ │ 2 tables │ │ Localiser palette fille │ │ nécessaires: │ │ compatible (TC) │ │ palette + │ │ │ │ adjacente │ │ Si aucune → anticiper │ │ pour excéd. │ │ position nouvelle PF │ └──────┬───────┘ └─────────────┬────────────┘ │ │ ▼ ▼ ┌──────────────┐ ┌──────────────────────────┐ │ Prio 1: centre│ │ Choisir table adjacente │ │ + côté │ │ à la PF : │ │ │ │ - déjà occupée par PS ? │ │ Prio 2: │ │ - VIDE ? (ping-pong) │ │ côté + │ │ - en évacuation ? │ │ centre │ │ - forcer évacuation │ │ │ │ │ │ Prio 3: │ └─────────────┬────────────┘ │ évacuation │ │ └──────┬───────┘ │ │ │ └────────┬───────────────┘ │ ▼ Table trouvée ? ┌────┴────┐ OUI NON │ │ ▼ ▼ Palette → Buffer dispo table PK (< MAX_PRELOAD) ? ┌────┴────┐ OUI NON │ │ ▼ ▼ Palette → Palette reste buffer ESx au PS (attente) ═══════════════════════════════════════════ Quand une table se libère au PK : → Chercher en buffer la palette avec le plus petit Line.CstAtt pour ce PK → Commander AGV : buffer → table ```