Skip to content
intermediate Phase 49 · Order Lifecycle

Invoice and Shipment

Invoice creation, shipment creation, partial shipment, and tracking in Magento 2

45m
0 problems
Topic Progress 0%

Invoice Creation

Invoice Service

namespace Magento\Sales\Model\Service;

class InvoiceService
{
    public function __construct(
        private \Magento\Sales\Api\InvoiceRepositoryInterface $invoiceRepository,
        private \Magento\Sales\Api\Data\InvoiceInterfaceFactory $invoiceFactory
    ) {}

    public function createInvoice(
        \Magento\Sales\Api\Data\OrderInterface $order,
        array $items = []
    ): \Magento\Sales\Api\Data\InvoiceInterface {
        // Create invoice from order
        $invoice = $this->invoiceFactory->create();
        $invoice->setOrderId($order->getEntityId());
        $invoice->setStoreId($order->getStoreId());
        $invoice->setBillingAddressId($order->getBillingAddressId());
        $invoice->setShippingAddressId($order->getShippingAddressId());

        // Add items
        if (empty($items)) {
            // Invoice all items
            foreach ($order->getItems() as $item) {
                $this->addItemToInvoice($invoice, $item);
            }
        } else {
            // Partial invoice - specific items
            foreach ($items as $itemId => $qty) {
                $item = $order->getItemById($itemId);
                if ($item) {
                    $this->addItemToInvoice($invoice, $item, $qty);
                }
            }
        }

        // Calculate totals
        $invoice->collectTotals();

        // Save
        $this->invoiceRepository->save($invoice);

        return $invoice;
    }
}

Invoice Items

private function addItemToInvoice(
    \Magento\Sales\Api\Data\InvoiceInterface $invoice,
    \Magento\Sales\Api\Data\OrderItemInterface $orderItem,
    float $qty = null
): void {
    $invoiceItem = $this->invoiceItemFactory->create();
    $invoiceItem->setOrderItem($orderItem);
    $invoiceItem->setQty($qty ?? $orderItem->getQtyOrdered());

    $invoice->addItem($invoiceItem);
}

Shipment Creation

Shipment Service

namespace Magento\Sales\Model\Service;

class ShipmentService
{
    public function __construct(
        private \Magento\Sales\Api\ShipmentRepositoryInterface $shipmentRepository,
        private \Magento\Sales\Api\Data\ShipmentInterfaceFactory $shipmentFactory,
        private \Magento\Sales\Api\Data\ShipmentTrackInterfaceFactory $trackFactory
    ) {}

    public function createShipment(
        \Magento\Sales\Api\Data\OrderInterface $order,
        array $items = [],
        array $trackingData = []
    ): \Magento\Sales\Api\Data\ShipmentInterface {
        $shipment = $this->shipmentFactory->create();
        $shipment->setOrderId($order->getEntityId());
        $shipment->setStoreId($order->getStoreId());

        // Add items
        if (empty($items)) {
            foreach ($order->getItems() as $item) {
                $this->addItemToShipment($shipment, $item);
            }
        } else {
            foreach ($items as $itemId => $qty) {
                $item = $order->getItemById($itemId);
                if ($item) {
                    $this->addItemToShipment($shipment, $item, $qty);
                }
            }
        }

        // Add tracking
        if (!empty($trackingData)) {
            foreach ($trackingData as $trackData) {
                $this->addTracking($shipment, $trackData);
            }
        }

        $this->shipmentRepository->save($shipment);

        return $shipment;
    }
}

Partial Shipment

Partial Shipment Implementation

// Ship specific items only
$items = [
    1 => 2,  // item_id 1, qty 2
    3 => 1,  // item_id 3, qty 1
];

$shipment = $this->shipmentService->createShipment($order, $items);

// Check remaining items
foreach ($order->getItems() as $item) {
    $shippedQty = $item->getQtyShipped();
    $remaining = $item->getQtyOrdered() - $shippedQty;

    if ($remaining > 0) {
        echo "Item {$item->getId()}: {$remaining} remaining\n";
    }
}

Multiple Shipments

// First shipment - items 1 and 2
$shipment1 = $this->shipmentService->createShipment(
    $order,
    [1 => 1, 2 => 2],
    [['carrier' => 'ups', 'number' => 'TRACK001']]
);

// Second shipment - remaining items
$shipment2 = $this->shipmentService->createShipment(
    $order,
    [1 => 1], // remaining qty of item 1
    [['carrier' => 'ups', 'number' => 'TRACK002']]
);

Shipment State

// Check if order can be shipped
if ($order->canShip()) {
    // Create shipment
}

// Check remaining qty to ship
foreach ($order->getItems() as $item) {
    $canShip = $item->getQtyToShip();
    if ($canShip > 0) {
        // Item can still be shipped
    }
}

Tracking Information

Adding Tracking

private function addTracking(
    \Magento\Sales\Api\Data\ShipmentInterface $shipment,
    array $trackData
): void {
    $track = $this->trackFactory->create();
    $track->setShipment($shipment);
    $track->setCarrierCode($trackData['carrier']);
    $track->setTitle($trackData['carrier_title'] ?? $trackData['carrier']);
    $track->setNumber($trackData['number']);

    $shipment->addTrack($track);
}

Tracking API

// Add tracking to existing shipment
$shipment = $this->shipmentRepository->get($shipmentId);

$track = $this->trackFactory->create();
$track->setCarrierCode('custom');
$track->setTitle('Custom Carrier');
$track->setNumber('TRACK123456');

$shipment->addTrack($track);
$this->shipmentRepository->save($shipment);

// Get tracking info
foreach ($shipment->getTracks() as $track) {
    echo 'Carrier: ' . $track->getCarrierCode();
    echo 'Number: ' . $track->getNumber();
    echo 'Status: ' . $track->getStatus();
}

Track Status

// Custom tracking status update
$track->setStatus('Delivered');
$track->setStatusDetail('Package delivered to front door');

// Available statuses
// pending, processing, shipped, delivered, error

Quiz

1. How do you create a partial invoice?

Question 1 options

2. Can an order have multiple shipments?

Question 2 options

3. How is tracking information added?

Question 3 options

Flashcards

Question

How to create invoice?

Answer

InvoiceService::createInvoice($order, $items)

Question

Partial shipment method?

Answer

Pass specific items array with quantities to shipment service

Question

Tracking storage?

Answer

ShipmentTrack objects with carrier_code, title, number

Question

Multiple shipments?

Answer

Yes, for partial shipments at different times

Revision Notes

Key Takeaways

  • 1. Invoices record payment for order items, can be full or partial
  • 2. Shipments record delivery of items, can be full or partial
  • 3. Tracking is added via ShipmentTrack objects on shipments
  • 4. Partial shipments allow items to be shipped at different times
  • 5. Order can track qty_invoiced and qty_shipped per item

Interview Tips

  • Explain the difference between invoice and shipment
  • Describe how partial invoicing and shipping work together
  • Discuss how tracking information is managed and displayed

Cheat Sheet

Invoice:
  Full: invoice all items
  Partial: specific item_id => qty
  Records payment

Shipment:
  Full: ship all items
  Partial: specific item_id => qty
  Multiple shipments allowed
  Tracks: carrier, number, status

API:
  InvoiceService::createInvoice()
  ShipmentService::createShipment()
  ShipmentTrack::setNumber()