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?
2. Can an order have multiple shipments?
3. How is tracking information added?
Flashcards
Question
How to create invoice?
Click to reveal answer
Answer
InvoiceService::createInvoice($order, $items)
Question
Partial shipment method?
Click to reveal answer
Answer
Pass specific items array with quantities to shipment service
Question
Tracking storage?
Click to reveal answer
Answer
ShipmentTrack objects with carrier_code, title, number
Question
Multiple shipments?
Click to reveal answer
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()