Trackily Docs

Fulfillment

TL;DR : marque une commande "préparée / expédiée / livrée" et attache éventuellement un tracking number + carrier via fulfill_native_order (Tier-2). Bulk via auto_fulfill_ready_orders (côté Shopify uniquement). fulfillment_status est orthogonal à payment_status — une commande peut être paid + unfulfilled, ou pending + fulfilled (rare).

Order detail avec actions fulfill

Concept

Le fulfillment, c'est tout ce qui se passe après l'encaissement côté merchant : préparer le colis, le remettre au transporteur, suivre la livraison, gérer un retour. Pour Trackily, c'est juste une transition d'état + un tracking optionnel — la logistique physique se passe ailleurs (en entrepôt, chez ton fournisseur dropship, etc.).

Les 6 statuts (fulfillment_status)

                                       ┌───────────────┐
                                       │  unfulfilled  │  ← default
                                       └───────┬───────┘
                                               │
                          ┌────────────────────┼────────────────────┐
                          │                    │                    │
                          ▼                    ▼                    ▼
                  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
                  │   partial    │    │  fulfilled   │    │  cancelled   │
                  │ (1+ items    │    │ (préparé,    │    │ (la commande │
                  │  parti, pas  │    │  pas forcé-  │    │  est annulée │
                  │  tout)       │    │  ment ship-  │    │  cancelled_at│
                  └──────┬───────┘    │  pé)         │    │  non NULL)   │
                         │            └──────┬───────┘    └──────────────┘
                         │                   │
                         └──────────┬────────┘
                                    ▼
                            ┌──────────────┐
                            │   shipped    │  ← tracking attaché
                            │ (parti, en   │
                            │  transit)    │
                            └──────┬───────┘
                                   │
                                   ▼
                            ┌──────────────┐
                            │  delivered   │  ← reçu par le client
                            └──────┬───────┘
                                   │
                                   ▼ (optionnel : retour)
                            ┌──────────────┐
                            │   returned   │  ← précède souvent un
                            │              │     refund total
                            └──────────────┘
Status Quand l'utiliser
unfulfilled Default. Rien n'a bougé.
partial Multi-items, certains partis, d'autres pas (rupture sur 1 variante par ex.).
fulfilled Préparé / picked. Utile pour les digitaux où "préparé" = "envoyé par email". Aussi utile en step intermédiaire avant shipped.
shipped Tracking number attaché, colis chez le transporteur.
delivered Confirmé livré. Peut être setté manuellement ou via un webhook transporteur (pas inclus out-of-box, à câbler).
returned Le colis est revenu. Étape pré-refund total typiquement.

Orthogonal au payment_status

payment_status fulfillment_status Cas typique
paid unfulfilled Commande à préparer
paid fulfilled Préparée, pas encore expédiée
paid shipped Cas nominal "tout va bien"
paid delivered Livraison confirmée
pending unfulfilled COD en attente cash, ou checkout pas finalisé
pending fulfilled COD préparée avant cash (rare — risque commerçant)
refunded returned Return-then-refund
refunded delivered Refund après-vente sans retour (geste commercial)
partial_refund shipped Partiel sur un produit livré OK

Tu peux librement combiner. C'est ta business logic qui décide ce qui a du sens.

Tracking : carrier + number

Deux colonnes optionnelles sur orders :

  • tracking_carrier (TEXT) — nom du transporteur ("DHL", "FedEx", "Bpost", "Colissimo")
  • tracking_number (TEXT) — le numéro

Côté affichage, l'admin UI et les emails post-shipping peuvent générer un lien de suivi vers le site du transporteur en fonction de tracking_carrier. Mapping géré dans les templates (non couvert ici).

Comment faire (UI + MCP)

Via l'admin UI

  1. Commerce → Orders → clique sur la commande.
  2. Bouton Fulfill en haut à droite.
  3. Modal :
    • Status — pick dans le dropdown (unfulfilled, partial, fulfilled, shipped, delivered, returned).
    • Tracking carrier — optionnel mais recommandé pour shipped.
    • Tracking number — optionnel.
    • Notify customer — si coché, déclenche l'email "Your order has shipped" (templates dans Settings → Email templates).
  4. Save.

Si tu passes en shipped sans tracking number, l'UI te demande confirmation ("Shipped without tracking?"). Pas bloquant, juste un nudge — beaucoup d'opérateurs n'utilisent pas de tracking pour les petits colis (Letterbox, lettres simples).

Via MCP

Outil principal : fulfill_native_order (Tier-2).

Passer en fulfilled simple :

{
  "name": "fulfill_native_order",
  "arguments": {
    "order_id": 87,
    "fulfillment_status": "fulfilled"
  }
}

Passer en shipped avec tracking :

{
  "name": "fulfill_native_order",
  "arguments": {
    "order_id": 87,
    "fulfillment_status": "shipped",
    "tracking_carrier": "Colissimo",
    "tracking_number": "1Z999AA10123456784"
  }
}

Tier-2 — première réponse preview :

{
  "status": "confirmation_required",
  "tool": "fulfill_native_order",
  "preview": {
    "order_id": 87,
    "order_number": "TR-000087",
    "current_fulfillment_status": "unfulfilled",
    "new_fulfillment_status": "shipped",
    "tracking_number": "1Z999AA10123456784",
    "tracking_carrier": "Colissimo"
  },
  "confirm_token": "tk_…"
}

Re-soumets avec le token pour exécuter.

Passer en delivered (confirmation manuelle) :

{
  "name": "fulfill_native_order",
  "arguments": {
    "order_id": 87,
    "fulfillment_status": "delivered"
  }
}

Passer en returned (étape pré-refund total) :

{
  "name": "fulfill_native_order",
  "arguments": {
    "order_id": 87,
    "fulfillment_status": "returned"
  }
}

Le tool ne déclenche aucune action côté provider de paiement. Si tu veux refunder à la suite, appelle refund_native_order séparément.

Bulk fulfillment : auto_fulfill_ready_orders

Important : auto_fulfill_ready_orders existe uniquement côté Shopify (cf. autopilot-ecommerce-orchestration-tools.js). Pour le natif, pas d'équivalent prêt à l'emploi. Pour automatiser en bulk natif, ton agent MCP peut chainer list_native_orders + N appels fulfill_native_order :

// Étape 1 : récupérer les commandes à fulfill
{
  "name": "list_native_orders",
  "arguments": {
    "payment_status": "paid",
    "fulfillment_status": "unfulfilled",
    "limit": 100
  }
}

Puis pour chaque order_id retourné, l'agent appelle fulfill_native_order en deux temps (preview + confirm). C'est intentionnellement plus verbeux que le bulk Shopify : la fulfillment native est souvent contextuelle (chaque colis a son propre tracking), automatiser sans review humaine est risqué.

Côté Shopify, le bulk auto-fulfill est utile parce que Shopify a la notion de "ready to fulfill" (paid + inventory available + no flag de fraude). Trackily n'a pas (encore) cette notion pour les natifs.

Shopify bulk : auto_fulfill_ready_orders

Pour rappel, côté Shopify :

{
  "name": "auto_fulfill_ready_orders",
  "arguments": {
    "store_id": 3,
    "limit": 50
  }
}

Preview :

{
  "status": "confirmation_required",
  "summary": "AUTO-FULFILL 22 ready orders on store \"My Store\". IRREVERSIBLE.",
  "preview": { "store_id": 3, "orders_to_fulfill": 22, "limit": 50 }
}

Confirm → loop sur les 22 commandes, appelle l'API Shopify pour chacune. Idempotent : ré-exécuter ne refait pas les fulfillments déjà créés (fulfillment_status='fulfilled' filtre les commandes déjà traitées).

Exemples concrets

1. Workflow standard : paid → shipped en deux étapes

Tu prépares le colis le matin, tu l'expédies l'après-midi :

// Matin — confirme la préparation (mais pas encore expédié)
{ "name": "fulfill_native_order", "arguments": { "order_id": 87, "fulfillment_status": "fulfilled" } }

// Après-midi — colis chez Colissimo
{
  "name": "fulfill_native_order",
  "arguments": {
    "order_id": 87,
    "fulfillment_status": "shipped",
    "tracking_carrier": "Colissimo",
    "tracking_number": "8L00012345678"
  }
}

L'email "Your order has shipped" est déclenché par la séquence email branchée sur l'event order.shipped (voir email/sequences.md).

2. Digital product : paid direct → fulfilled

Le produit est un ebook PDF. Au paiement, ta séquence email envoie immédiatement le lien de download. Tu fulfilles juste pour la propreté du statut :

{
  "name": "fulfill_native_order",
  "arguments": {
    "order_id": 87,
    "fulfillment_status": "fulfilled"
  }
}

Pas de tracking (digital). Pas de shipping cost non plus (calcul shipping skip sur type='digital').

3. Return-then-refund

Le client renvoie le produit, tu reçois le colis le 25/05 :

// 1. Marquer returned
{ "name": "fulfill_native_order", "arguments": { "order_id": 87, "fulfillment_status": "returned" } }

// 2. Refund total
{ "name": "refund_native_order", "arguments": { "order_id": 87 } }

payment_status passe à refunded, stock restocké (parce que c'est un full refund), fulfillment_status reste à returned.

4. Partial shipping (multi-item)

Commande de 3 items, dont 1 en rupture. Tu envoies les 2 disponibles :

// Étape 1 — passer en partial
{ "name": "fulfill_native_order", "arguments": { "order_id": 87, "fulfillment_status": "partial", "tracking_number": "8L00012345678", "tracking_carrier": "Colissimo" } }

// Étape 2 (3 semaines plus tard, stock rechargé) — envoie du dernier item
// Idéalement tu enverrais comme un 2e colis avec un 2e tracking number, mais
// Trackily n'a qu'un seul champ tracking_number par order — un soit-tu update
// le tracking, soit tu joins les deux dans le même champ ("8L0001 + 8L0002").
{ "name": "fulfill_native_order", "arguments": { "order_id": 87, "fulfillment_status": "shipped" } }

Multi-tracking par commande n'est pas supporté natif. Si c'est un cas fréquent pour toi, ouvre une issue.

5. Delivered manuel après confirmation client

Le client te dit par email "Bien reçu, merci !". Tu confirmes :

{ "name": "fulfill_native_order", "arguments": { "order_id": 87, "fulfillment_status": "delivered" } }

Utile pour les rapports "lead time entre paid et delivered" → diagnose les lenteurs côté logistique.

Stratégies fulfillment

  • Toujours attacher un tracking sur shipped — la conversion d'un email "shipped + tracking link" est 2× supérieure à un email "shipped" simple. Le client peut suivre, il ne contacte pas le support.
  • Email automatique sur chaque transitionunfulfilled → fulfilled, fulfilled → shipped, shipped → delivered. 3 emails, 3 occasions de garder le client engagé. Voir email/sequences.md.
  • Auto-fulfill les digitaux — crée une rule Automizer : payment_status changed to paid AND product.type='digital'fulfill_native_order(fulfillment_status='fulfilled'). Te libère 100 % du fulfillment digital.
  • Auto-mark delivered J+10 sur shipped — règle Automizer : fulfillment_status='shipped' AND fulfilled_at < NOW() - INTERVAL '10 days'fulfill_native_order(fulfillment_status='delivered'). Pas optimal (un colis peut prendre + de 10 jours) mais clean tes stats.
  • Track ta vitesse paid → shipped — KPI "time to shipping" en heures. > 48h, ton service client va manger des réclamations. < 24h, c'est très bien.

Erreurs courantes

  • "fulfillment_status must be one of: unfulfilled, partial, fulfilled, shipped, delivered, returned" — tu as passé une valeur hors enum.
  • "Cannot fulfill a cancelled order"cancelled_at est non-NULL. Tu ne peux pas fulfiller une commande annulée. Crée une nouvelle commande ou revert le cancel (en SQL direct — pas de tool pour ça parce que c'est sale).
  • "Stock négatif après fulfill" — pas possible via l'UI, mais possible si tu update directement en SQL. Trackily ne vérifie pas le stock à la fulfill, parce que le stock a été décrémenté à la création de la commande, pas à la fulfill.
  • "Le client n'a pas reçu l'email shipping" — vérifie que la séquence email "order shipped" est attachée à la campagne, que post_purchase_list_id est setté, et que le buyer n'est pas dans email_suppression.
  • "Tracking number copié-collé contient un saut de ligne" — l'UI trim côté front, mais via MCP c'est de ta responsabilité. String.prototype.trim() avant envoi.
  • "Carrier "DHL" affiche un lien tracking cassé" — le mapping carrier → tracking URL est dans les templates email. Vérifie l'orthographe ("dhl", "DHL", "Dhl" peuvent ne pas tous matcher selon ta normalisation).
  • "Auto-fulfill natif n'existe pas" — confirmé, à automatiser via boucle MCP côté agent (voir section bulk ci-dessus).

Voir aussi

  • orders.md — la state machine complète et payment_status vs fulfillment_status.
  • refunds.md — la suite logique après fulfillment_status='returned'.
  • Email → Sequences — séquences "order shipped" / "delivery confirmed".
  • Automizer → Rules — auto-fulfill via rules natives + fulfill_native_order enchainés.
  • Code source : autopilot-native-orders-tools.js toolFulfillNativeOrder, autopilot-ecommerce-orchestration-tools.js toolAutoFulfillReadyOrders (Shopify uniquement).