/order — Commandes Sylius
Tu aides à créer, manipuler ou faire transiter une commande Sylius. Le modèle Order est le point de convergence de Sylius : il représente aussi bien un panier actif qu'une commande passée, tient une collection d'OrderItem, et porte trois state machines (état principal, shipping, payment).
Référence officielle : docs.sylius.com/the-book/carts-and-orders/orders · checkout · shipments.
Détection préalable (obligatoire)
- Lire
composer.json à la racine.
- Vérifier
sylius/sylius (ou sylius/order-bundle / sylius/core-bundle) dans les dépendances.
- Présent → OK.
- Absent → « Ce skill cible Sylius. Je ne trouve pas
sylius/sylius dans composer.json. Tu confirmes qu'on continue ? »
- Si la manipulation doit déclencher un envoi d'e-mail (confirmation, expédition) → enchaîner avec
/sylius:email.
- Si la demande est « ajouter un state / une transition au cycle de vie » (ex. nouveau state
delivered_to_relay, retirer skip_shipping, brancher un listener sur workflow.sylius_order.completed.fulfill) → basculer sur /sylius:state-machine : ici on applique les transitions existantes, on ne modifie pas la machine elle-même.
- Si la manipulation implique une remise / un ajustement manuel sur l'order → voir
/sylius:adjustment (manuel) ou /sylius:cart-promotion (automatique par règles).
Composants clés
| Composant |
Service / Classe |
Rôle |
| Factory Order |
sylius.factory.order |
Crée une nouvelle instance OrderInterface |
| Factory OrderItem |
sylius.factory.order_item |
Crée un item à rattacher à l'order |
| QuantityModifier |
sylius.modifier.order_item_quantity |
Change la quantité d'un item (recalcule les totaux) |
| OrderProcessor |
sylius.order_processing.order_processor |
Recalcule tout (prix, taxes, promos, shipping, adjustments) |
| Repository Order |
sylius.repository.order |
Persiste l'order |
| Manager Order |
sylius.manager.order |
Flush Doctrine |
| Context channel |
sylius.context.channel |
Récupère le canal courant |
| Context locale |
sylius.context.locale |
Récupère la locale courante |
| Context currency |
sylius.context.currency |
Récupère la devise courante |
| State machine factory |
sm.factory |
Construit les state machines (order, shipping, payment) |
État du cycle de vie
| State machine |
Graph |
États |
Transitions principales |
| Order principal |
sylius_order |
cart, new, fulfilled, cancelled |
create (cart → new), fulfill, cancel |
| Shipping |
OrderShippingTransitions::GRAPH |
cart, ready, shipped, cancelled |
TRANSITION_REQUEST_SHIPPING, TRANSITION_SHIP, TRANSITION_CANCEL |
| Payment |
OrderPaymentTransitions::GRAPH |
cart, awaiting_payment, partially_paid, paid, cancelled, refunded |
TRANSITION_REQUEST_PAYMENT, TRANSITION_PAY, TRANSITION_REFUND |
Règles fondamentales
- Toujours passer par les factories (
sylius.factory.order, sylius.factory.order_item) — jamais new Order() direct. Les factories posent les valeurs par défaut et les events Sylius.
- Channel + locale + currency obligatoires avant toute opération sur un order. Sinon l'
OrderProcessor lève silencieusement des erreurs de calcul (totaux à 0, shipping méthodes filtrées à vide).
- Quantité via le modifier, jamais
$orderItem->setQuantity(3) direct — le modifier recalcule les unit prices, adjustments, stock. Même pour un item neuf à quantité 1 → passer par le modifier.
- Appeler
OrderProcessor::process() après chaque mutation (ajout item, ajout shipment, changement de quantité). C'est idempotent et c'est lui qui recalcule tout l'order (totaux, taxes, promos, shipping costs, adjustments).
canBeProcessed = state === 'cart' par défaut. Si tu dois retoucher un order new ou fulfilled, override cette méthode ou utilise un OrderProcessor dédié — ne force pas l'état.
- State machines via
sm.factory : passer par le factory, jamais modifier state directement sur l'entité — les listeners (e-mails, stock, audit) s'abonnent aux transitions, pas aux changements de colonne.
- Flush à la fin, pas entre chaque mutation : accumuler, puis
manager.order->flush() une seule fois — le OrderProcessor est suffisamment coûteux pour qu'on évite les flush intermédiaires.
- Persist une fois :
$repository->add($order) ne doit être appelé que sur un order neuf. Les modifications ultérieures passent par le manager (déjà tracké par l'UoW Doctrine).
Déroulement
Cas 1 — Créer un order programmatique complet
use Sylius\Component\Core\Model\OrderInterface;
use Sylius\Component\Resource\Factory\FactoryInterface;
final class CreateOrderForCustomer
{
public function __construct(
private FactoryInterface $orderFactory,
private FactoryInterface $orderItemFactory,
private OrderItemQuantityModifierInterface $quantityModifier,
private OrderProcessorInterface $orderProcessor,
private OrderRepositoryInterface $orderRepository,
private ChannelContextInterface $channelContext,
private LocaleContextInterface $localeContext,
private CurrencyContextInterface $currencyContext,
private ProductVariantRepositoryInterface $variantRepository,
private CustomerRepositoryInterface $customerRepository,
) {}
public function __invoke(string $customerEmail, string $variantCode, int $quantity): OrderInterface
{
/** @var OrderInterface $order */
$order = $this->orderFactory->createNew();
$order->setChannel($this->channelContext->getChannel());
$order->setLocaleCode($this->localeContext->getLocaleCode());
$order->setCurrencyCode($this->currencyContext->getCurrencyCode());
$customer = $this->customerRepository->findOneBy(['email' => $customerEmail]);
$order->setCustomer($customer);
$variant = $this->variantRepository->findOneBy(['code' => $variantCode]);
$orderItem = $this->orderItemFactory->createNew();
$orderItem->setVariant($variant);
$this->quantityModifier->modify($orderItem, $quantity);
$order->addItem($orderItem);
$this->orderProcessor->process($order);
$this->orderRepository->add($order);
return $order;
}
}
Les services à injecter correspondent aux entrées de la table « Composants clés ». Les interfaces vivent dans Sylius\Component\Core\* et Sylius\Component\Order\*.
Cas 2 — Ajouter un shipment et expédier
use Sylius\Component\Core\OrderShippingTransitions;
final class ShipOrder
{
public function __construct(
private FactoryInterface $shipmentFactory,
private ShippingMethodRepositoryInterface $shippingMethodRepository,
private OrderProcessorInterface $orderProcessor,
private StateMachineFactoryInterface $stateMachineFactory,
private ObjectManager $orderManager,
) {}
public function __invoke(OrderInterface $order, string $shippingMethodCode): void
{
$shipment = $this->shipmentFactory->createNew();
$shipment->setMethod($this->shippingMethodRepository->findOneBy(['code' => $shippingMethodCode]));
$order->addShipment($shipment);
$this->orderProcessor->process($order);
$stateMachine = $this->stateMachineFactory->get($order, OrderShippingTransitions::GRAPH);
$stateMachine->apply(OrderShippingTransitions::TRANSITION_REQUEST_SHIPPING);
$stateMachine->apply(OrderShippingTransitions::TRANSITION_SHIP);
$this->orderManager->flush();
}
}
Adjustments de shipping : le coût du shipment est posé comme Adjustment sur l'order par OrderProcessor, pas en dur sur le shipment — si tu inspectes les totaux, lire via $order->getAdjustments() filtré par type shipping.
Cas 3 — Ajouter un payment et le régler
use Sylius\Component\Core\OrderPaymentTransitions;
final class PayOrder
{
public function __construct(
private FactoryInterface $paymentFactory,
private PaymentMethodRepositoryInterface $paymentMethodRepository,
private StateMachineFactoryInterface $stateMachineFactory,
private ObjectManager $orderManager,
) {}
public function __invoke(OrderInterface $order, string $paymentMethodCode): void
{
$payment = $this->paymentFactory->createNew();
$payment->setMethod($this->paymentMethodRepository->findOneBy(['code' => $paymentMethodCode]));
$payment->setCurrencyCode($order->getCurrencyCode());
$order->addPayment($payment);
$stateMachine = $this->stateMachineFactory->get($order, OrderPaymentTransitions::GRAPH);
$stateMachine->apply(OrderPaymentTransitions::TRANSITION_REQUEST_PAYMENT);
$stateMachine->apply(OrderPaymentTransitions::TRANSITION_PAY);
$this->orderManager->flush();
}
}
Si plusieurs payments sont attachés à l'order, le paymentState de l'order passe à partially_paid tant que tous ne sont pas completed.
Cas 4 — Annuler un order
use Sylius\Component\Core\OrderTransitions;
$stateMachine = $this->stateMachineFactory->get($order, OrderTransitions::GRAPH);
if ($stateMachine->can(OrderTransitions::TRANSITION_CANCEL)) {
$stateMachine->apply(OrderTransitions::TRANSITION_CANCEL);
$this->orderManager->flush();
}
Toujours tester can() avant apply() — un order déjà fulfilled ne peut plus être cancelled par cette transition. Lever une exception métier dédiée si l'état ne permet pas l'action.
Cas 5 — Retoucher un order existant (attention à canBeProcessed)
Par défaut, OrderProcessor::process() no-op sur un order dont state !== 'cart'. Pour une opération admin qui réajuste une commande new (ex. changer la quantité d'un item), deux options :
- Dédier un processor implémentant
OrderProcessorInterface sans la garde canBeProcessed, injecté explicitement.
- Override
Order::canBeProcessed() dans ton entité custom pour élargir le set d'états autorisés.
Ne jamais passer l'order en cart juste pour le re-processer puis le remettre en new — tu déclenches tous les listeners de cart → new (e-mails de confirmation renvoyés, stock réajusté, etc.).
Pièges fréquents
- Totaux à 0 après création : il manque un des trois contextes (channel, locale, currency) ou tu n'as pas appelé
OrderProcessor::process() après avoir ajouté les items. L'ordre : set contextes → add items → process() → add().
- Shipping méthodes vides :
$order->getShipments()[0]->getMethod() retourne null parce que la méthode n'existe pas dans ce canal. Les méthodes sont scopées par channel — vérifier shipping_methods de la table sylius_channel_shipping_methods.
setQuantity() direct au lieu du modifier : les totaux ne sont pas recalculés et le stock n'est pas ajusté. Toujours OrderItemQuantityModifier::modify($item, $qty).
- Transition refusée silencieusement :
apply() lève \SM\SMException si la transition n'est pas possible. Toujours can() d'abord, ou catcher l'exception et retourner une erreur métier claire.
sm.factory manquant en Symfony 7 : Sylius est en train de migrer vers Symfony Workflow. Selon la version, il faut injecter StateMachineInterface (abstraction Sylius) plutôt que sm.factory direct. Vérifier la version de sylius/sylius avant d'écrire le code.
- Guest order sans customer :
$order->getCustomer() peut être null pour un panier anonyme. Checker avant ->getEmail() ou ->getId(). Sylius bascule un guest en customer au moment du checkout, pas avant.
- Double flush via OrderProcessor :
process() ne flush pas. Si tu appelles flush() entre chaque mutation, tu perds le bénéfice de l'UoW — accumuler puis flush() une fois à la fin.
- Fixtures qui créent des orders en
cart : normal, c'est l'état par défaut. Pour simuler une commande réelle dans un fixture, faire transiter explicitement via OrderTransitions::TRANSITION_CREATE après avoir complété panier + shipping + payment.
$order->getTotal() après addItem() sans process() : retourne 0. Le total est recalculé par OrderProcessor, pas par l'addItem. Ordre : add → process → read.
Clôture
Afficher :
- Opération réalisée (création, ajout shipment, transition, etc.) et état final des trois states (
order.state, order.shippingState, order.paymentState).
- Services injectés dans le code proposé.
- Fichiers touchés.
- Ce qui reste : fixtures de test, appels e-mail (
/sylius:email order_confirmation), ou listeners à câbler côté métier.
Argument optionnel
/sylius:order create — cadre la création d'un order programmatique complet (Cas 1).
/sylius:order ship — cadre l'ajout d'un shipment et les transitions shipping (Cas 2).
/sylius:order pay — cadre l'ajout d'un payment et les transitions payment (Cas 3).
/sylius:order cancel — cadre l'annulation d'un order existant (Cas 4).
/sylius:order sans argument — demande le cas d'usage (création, shipping, payment, cancel, retouche).
1---2name: order3description: Order4---56# /order — Commandes Sylius78Tu aides à **créer, manipuler ou faire transiter une commande Sylius**. Le modèle `Order` est le point de convergence de Sylius : il représente aussi bien un panier actif qu'une commande passée, tient une collection d'`OrderItem`, et porte trois state machines (état principal, shipping, payment).910Référence officielle : [docs.sylius.com/the-book/carts-and-orders/orders](https://docs.sylius.com/the-book/carts-and-orders/orders) · [checkout](https://docs.sylius.com/the-book/carts-and-orders/checkout) · [shipments](https://docs.sylius.com/the-book/carts-and-orders/shipments).1112## Détection préalable (obligatoire)13141. Lire `composer.json` à la racine.152. Vérifier `sylius/sylius` (ou `sylius/order-bundle` / `sylius/core-bundle`) dans les dépendances.16 - Présent → OK.17 - Absent → *« Ce skill cible Sylius. Je ne trouve pas `sylius/sylius` dans composer.json. Tu confirmes qu'on continue ? »*183. Si la manipulation doit déclencher un envoi d'e-mail (confirmation, expédition) → enchaîner avec `/sylius:email`.194. Si la demande est *« ajouter un state / une transition au cycle de vie »* (ex. nouveau state `delivered_to_relay`, retirer `skip_shipping`, brancher un listener sur `workflow.sylius_order.completed.fulfill`) → basculer sur **`/sylius:state-machine`** : ici on *applique* les transitions existantes, on ne modifie pas la machine elle-même.205. Si la manipulation implique une remise / un ajustement manuel sur l'order → voir `/sylius:adjustment` (manuel) ou `/sylius:cart-promotion` (automatique par règles).2122## Composants clés2324| Composant | Service / Classe | Rôle |25|-----------|------------------|------|26| Factory Order | `sylius.factory.order` | Crée une nouvelle instance `OrderInterface` |27| Factory OrderItem | `sylius.factory.order_item` | Crée un item à rattacher à l'order |28| QuantityModifier | `sylius.modifier.order_item_quantity` | Change la quantité d'un item (recalcule les totaux) |29| OrderProcessor | `sylius.order_processing.order_processor` | Recalcule tout (prix, taxes, promos, shipping, adjustments) |30| Repository Order | `sylius.repository.order` | Persiste l'order |31| Manager Order | `sylius.manager.order` | Flush Doctrine |32| Context channel | `sylius.context.channel` | Récupère le canal courant |33| Context locale | `sylius.context.locale` | Récupère la locale courante |34| Context currency | `sylius.context.currency` | Récupère la devise courante |35| State machine factory | `sm.factory` | Construit les state machines (order, shipping, payment) |3637## État du cycle de vie3839| State machine | Graph | États | Transitions principales |40|---------------|-------|-------|-------------------------|41| Order principal | `sylius_order` | `cart`, `new`, `fulfilled`, `cancelled` | `create` (cart → new), `fulfill`, `cancel` |42| Shipping | `OrderShippingTransitions::GRAPH` | `cart`, `ready`, `shipped`, `cancelled` | `TRANSITION_REQUEST_SHIPPING`, `TRANSITION_SHIP`, `TRANSITION_CANCEL` |43| Payment | `OrderPaymentTransitions::GRAPH` | `cart`, `awaiting_payment`, `partially_paid`, `paid`, `cancelled`, `refunded` | `TRANSITION_REQUEST_PAYMENT`, `TRANSITION_PAY`, `TRANSITION_REFUND` |4445## Règles fondamentales4647- **Toujours passer par les factories** (`sylius.factory.order`, `sylius.factory.order_item`) — jamais `new Order()` direct. Les factories posent les valeurs par défaut et les events Sylius.48- **Channel + locale + currency obligatoires** avant toute opération sur un order. Sinon l'`OrderProcessor` lève silencieusement des erreurs de calcul (totaux à 0, shipping méthodes filtrées à vide).49- **Quantité via le modifier**, jamais `$orderItem->setQuantity(3)` direct — le modifier recalcule les unit prices, adjustments, stock. Même pour un item neuf à quantité 1 → passer par le modifier.50- **Appeler `OrderProcessor::process()` après chaque mutation** (ajout item, ajout shipment, changement de quantité). C'est idempotent et c'est lui qui recalcule tout l'order (totaux, taxes, promos, shipping costs, adjustments).51- **`canBeProcessed` = `state === 'cart'` par défaut**. Si tu dois retoucher un order `new` ou `fulfilled`, override cette méthode ou utilise un `OrderProcessor` dédié — ne force pas l'état.52- **State machines via `sm.factory`** : passer par le factory, jamais modifier `state` directement sur l'entité — les listeners (e-mails, stock, audit) s'abonnent aux transitions, pas aux changements de colonne.53- **Flush à la fin, pas entre chaque mutation** : accumuler, puis `manager.order->flush()` une seule fois — le `OrderProcessor` est suffisamment coûteux pour qu'on évite les flush intermédiaires.54- **Persist une fois** : `$repository->add($order)` ne doit être appelé que sur un order neuf. Les modifications ultérieures passent par le manager (déjà tracké par l'UoW Doctrine).5556## Déroulement5758### Cas 1 — Créer un order programmatique complet5960```php61use Sylius\Component\Core\Model\OrderInterface;62use Sylius\Component\Resource\Factory\FactoryInterface;6364final class CreateOrderForCustomer65{66 public function __construct(67 private FactoryInterface $orderFactory,68 private FactoryInterface $orderItemFactory,69 private OrderItemQuantityModifierInterface $quantityModifier,70 private OrderProcessorInterface $orderProcessor,71 private OrderRepositoryInterface $orderRepository,72 private ChannelContextInterface $channelContext,73 private LocaleContextInterface $localeContext,74 private CurrencyContextInterface $currencyContext,75 private ProductVariantRepositoryInterface $variantRepository,76 private CustomerRepositoryInterface $customerRepository,77 ) {}7879 public function __invoke(string $customerEmail, string $variantCode, int $quantity): OrderInterface80 {81 /** @var OrderInterface $order */82 $order = $this->orderFactory->createNew();8384 $order->setChannel($this->channelContext->getChannel());85 $order->setLocaleCode($this->localeContext->getLocaleCode());86 $order->setCurrencyCode($this->currencyContext->getCurrencyCode());8788 $customer = $this->customerRepository->findOneBy(['email' => $customerEmail]);89 $order->setCustomer($customer);9091 $variant = $this->variantRepository->findOneBy(['code' => $variantCode]);92 $orderItem = $this->orderItemFactory->createNew();93 $orderItem->setVariant($variant);94 $this->quantityModifier->modify($orderItem, $quantity);95 $order->addItem($orderItem);9697 $this->orderProcessor->process($order);98 $this->orderRepository->add($order);99100 return $order;101 }102}103```104105Les services à injecter correspondent aux entrées de la table « Composants clés ». Les interfaces vivent dans `Sylius\Component\Core\*` et `Sylius\Component\Order\*`.106107### Cas 2 — Ajouter un shipment et expédier108109```php110use Sylius\Component\Core\OrderShippingTransitions;111112final class ShipOrder113{114 public function __construct(115 private FactoryInterface $shipmentFactory,116 private ShippingMethodRepositoryInterface $shippingMethodRepository,117 private OrderProcessorInterface $orderProcessor,118 private StateMachineFactoryInterface $stateMachineFactory,119 private ObjectManager $orderManager,120 ) {}121122 public function __invoke(OrderInterface $order, string $shippingMethodCode): void123 {124 $shipment = $this->shipmentFactory->createNew();125 $shipment->setMethod($this->shippingMethodRepository->findOneBy(['code' => $shippingMethodCode]));126 $order->addShipment($shipment);127128 $this->orderProcessor->process($order);129130 $stateMachine = $this->stateMachineFactory->get($order, OrderShippingTransitions::GRAPH);131 $stateMachine->apply(OrderShippingTransitions::TRANSITION_REQUEST_SHIPPING);132 $stateMachine->apply(OrderShippingTransitions::TRANSITION_SHIP);133134 $this->orderManager->flush();135 }136}137```138139**Adjustments de shipping** : le coût du shipment est posé comme `Adjustment` sur l'order par `OrderProcessor`, pas en dur sur le shipment — si tu inspectes les totaux, lire via `$order->getAdjustments()` filtré par type `shipping`.140141### Cas 3 — Ajouter un payment et le régler142143```php144use Sylius\Component\Core\OrderPaymentTransitions;145146final class PayOrder147{148 public function __construct(149 private FactoryInterface $paymentFactory,150 private PaymentMethodRepositoryInterface $paymentMethodRepository,151 private StateMachineFactoryInterface $stateMachineFactory,152 private ObjectManager $orderManager,153 ) {}154155 public function __invoke(OrderInterface $order, string $paymentMethodCode): void156 {157 $payment = $this->paymentFactory->createNew();158 $payment->setMethod($this->paymentMethodRepository->findOneBy(['code' => $paymentMethodCode]));159 $payment->setCurrencyCode($order->getCurrencyCode());160 $order->addPayment($payment);161162 $stateMachine = $this->stateMachineFactory->get($order, OrderPaymentTransitions::GRAPH);163 $stateMachine->apply(OrderPaymentTransitions::TRANSITION_REQUEST_PAYMENT);164 $stateMachine->apply(OrderPaymentTransitions::TRANSITION_PAY);165166 $this->orderManager->flush();167 }168}169```170171Si plusieurs payments sont attachés à l'order, le `paymentState` de l'order passe à `partially_paid` tant que tous ne sont pas `completed`.172173### Cas 4 — Annuler un order174175```php176use Sylius\Component\Core\OrderTransitions;177178$stateMachine = $this->stateMachineFactory->get($order, OrderTransitions::GRAPH);179if ($stateMachine->can(OrderTransitions::TRANSITION_CANCEL)) {180 $stateMachine->apply(OrderTransitions::TRANSITION_CANCEL);181 $this->orderManager->flush();182}183```184185Toujours tester `can()` avant `apply()` — un order déjà `fulfilled` ne peut plus être `cancelled` par cette transition. Lever une exception métier dédiée si l'état ne permet pas l'action.186187### Cas 5 — Retoucher un order existant (attention à `canBeProcessed`)188189Par défaut, `OrderProcessor::process()` no-op sur un order dont `state !== 'cart'`. Pour une opération admin qui réajuste une commande `new` (ex. changer la quantité d'un item), deux options :1901911. **Dédier un processor** implémentant `OrderProcessorInterface` sans la garde `canBeProcessed`, injecté explicitement.1922. **Override `Order::canBeProcessed()`** dans ton entité custom pour élargir le set d'états autorisés.193194Ne **jamais** passer l'order en `cart` juste pour le re-processer puis le remettre en `new` — tu déclenches tous les listeners de `cart → new` (e-mails de confirmation renvoyés, stock réajusté, etc.).195196## Pièges fréquents197198- **Totaux à 0 après création** : il manque un des trois contextes (channel, locale, currency) ou tu n'as pas appelé `OrderProcessor::process()` après avoir ajouté les items. L'ordre : set contextes → add items → `process()` → `add()`.199- **Shipping méthodes vides** : `$order->getShipments()[0]->getMethod()` retourne `null` parce que la méthode n'existe pas dans ce canal. Les méthodes sont scopées par channel — vérifier `shipping_methods` de la table `sylius_channel_shipping_methods`.200- **`setQuantity()` direct au lieu du modifier** : les totaux ne sont pas recalculés et le stock n'est pas ajusté. Toujours `OrderItemQuantityModifier::modify($item, $qty)`.201- **Transition refusée silencieusement** : `apply()` lève `\SM\SMException` si la transition n'est pas possible. Toujours `can()` d'abord, ou catcher l'exception et retourner une erreur métier claire.202- **`sm.factory` manquant en Symfony 7** : Sylius est en train de migrer vers Symfony Workflow. Selon la version, il faut injecter `StateMachineInterface` (abstraction Sylius) plutôt que `sm.factory` direct. Vérifier la version de `sylius/sylius` avant d'écrire le code.203- **Guest order sans customer** : `$order->getCustomer()` peut être `null` pour un panier anonyme. Checker avant `->getEmail()` ou `->getId()`. Sylius bascule un guest en customer au moment du checkout, pas avant.204- **Double flush via OrderProcessor** : `process()` ne flush pas. Si tu appelles `flush()` entre chaque mutation, tu perds le bénéfice de l'UoW — accumuler puis `flush()` une fois à la fin.205- **Fixtures qui créent des orders en `cart`** : normal, c'est l'état par défaut. Pour simuler une commande réelle dans un fixture, faire transiter explicitement via `OrderTransitions::TRANSITION_CREATE` après avoir complété panier + shipping + payment.206- **`$order->getTotal()` après `addItem()` sans `process()`** : retourne 0. Le total est recalculé par `OrderProcessor`, pas par l'`addItem`. Ordre : add → process → read.207208## Clôture209210Afficher :211- Opération réalisée (création, ajout shipment, transition, etc.) et état final des trois states (`order.state`, `order.shippingState`, `order.paymentState`).212- Services injectés dans le code proposé.213- Fichiers touchés.214- Ce qui reste : fixtures de test, appels e-mail (`/sylius:email order_confirmation`), ou listeners à câbler côté métier.215216## Argument optionnel217218`/sylius:order create` — cadre la création d'un order programmatique complet (Cas 1).219220`/sylius:order ship` — cadre l'ajout d'un shipment et les transitions shipping (Cas 2).221222`/sylius:order pay` — cadre l'ajout d'un payment et les transitions payment (Cas 3).223224`/sylius:order cancel` — cadre l'annulation d'un order existant (Cas 4).225226`/sylius:order` sans argument — demande le cas d'usage (création, shipping, payment, cancel, retouche).