Hello Magento mates,
Invoices are critical documents in every eCommerce business, providing details of product purchases, pricing, taxes, and other relevant information. One of the key customization that merchants often seek is enhancing their invoice PDFs to provide more detailed information, such as the number of products purchased.
This seemingly small feature significantly improves the clarity and organization of invoices for both customers and sellers.
By default, Magento 2 generates clean and structured invoices, but businesses often need more customized layouts to suit their operational needs. Today, in this blog, we will learn to add the number of products on every page of the invoice PDF.
Contents
Step 1 – First, we need to create a “di.xml” file inside our extension at the following path.
app/code/Vendor/Extension/etc/di.xml
Then enter the following code
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Sales\Model\Order\Pdf\Invoice" type="Vendor\Extension\Model\Order\Pdf\Invoices" /> </config>
Step 2 – Following this, we need to create an “Invoices.php” file inside ur extension at the following path.
app/code/Vendor/Extension/Model/Order/Pdf/Invoices.php
After that, enter the following code
<?php namespace Vendor\Extension\Model\Order\Pdf; use Magento\Framework\Locale\ResolverInterface; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Sales\Model\Order\Pdf\AbstractPdf; use Magento\Payment\Helper\Data ; use Magento\Framework\Stdlib\StringUtils; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Filesystem; use Magento\Sales\Model\Order\Pdf\Config; use Magento\Sales\Model\Order\Pdf\Total\Factory; use Magento\Sales\Model\Order\Pdf\ItemsFactory; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Framework\Translate\Inline\StateInterface; use Magento\Sales\Model\Order\Address\Renderer; use Magento\Store\Model\StoreManagerInterface; use Magento\Store\Model\App\Emulation; use Magento\Sales\Model\RtlTextHandler; use Magento\Framework\App\ObjectManager; class Invoices extends \Magento\Sales\Model\Order\Pdf\Invoice { /* @var \Magento\Store\Model\StoreManagerInterface */ protected $_storeManager; protected $localeDate; /** * @var \Magento\Store\Model\App\Emulation */ private $appEmulation; protected $_scopeConfig; private $rtlTextHandler; /** * @param \Magento\Payment\Helper\Data $paymentData * @param \Magento\Framework\Stdlib\StringUtils $string * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Framework\Filesystem $filesystem * @param Config $pdfConfig * @param \Magento\Sales\Model\Order\Pdf\Total\Factory $pdfTotalFactory * @param \Magento\Sales\Model\Order\Pdf\ItemsFactory $pdfItemsFactory * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate * @param \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation * @param \Magento\Sales\Model\Order\Address\Renderer $addressRenderer * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Store\Model\App\Emulation $appEmulation * @param array $data * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Payment\Helper\Data $paymentData, \Magento\Framework\Stdlib\StringUtils $string, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, \Magento\Framework\Filesystem $filesystem, \Magento\Sales\Model\Order\Pdf\Config $pdfConfig, \Magento\Sales\Model\Order\Pdf\Total\Factory $pdfTotalFactory, \Magento\Sales\Model\Order\Pdf\ItemsFactory $pdfItemsFactory, \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation, \Magento\Sales\Model\Order\Address\Renderer $addressRenderer, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Store\Model\App\Emulation $appEmulation, ?RtlTextHandler $rtlTextHandler = null, array $data = [] ) { $this->_storeManager = $storeManager; $this->appEmulation = $appEmulation; $this->_scopeConfig = $scopeConfig; $this->rtlTextHandler = $rtlTextHandler ?: ObjectManager::getInstance()->get(RtlTextHandler::class); parent::__construct( $paymentData, $string, $scopeConfig, $filesystem, $pdfConfig, $pdfTotalFactory, $pdfItemsFactory, $localeDate, $inlineTranslation, $addressRenderer, $storeManager, $appEmulation, $data ); } public function getPdf($invoices = []) { $this->_beforeGetPdf(); $this->_initRenderer('invoice'); $pdf = new \Zend_Pdf(); $this->_setPdf($pdf); $style = new \Zend_Pdf_Style(); $this->_setFontBold($style, 10); foreach ($invoices as $invoice) { if ($invoice->getStoreId()) { $this->appEmulation->startEnvironmentEmulation( $invoice->getStoreId(), \Magento\Framework\App\Area::AREA_FRONTEND, true ); $this->_storeManager->setCurrentStore($invoice->getStoreId()); } $page = $this->newPage(); $order = $invoice->getOrder(); /* Add image */ $this->insertLogo($page, $invoice->getStore()); /* Add address */ $this->insertAddress($page, $invoice->getStore()); /* Add head */ $this->insertOrder( $page, $order, $this->_scopeConfig->isSetFlag( self::XML_PATH_SALES_PDF_INVOICE_PUT_ORDER_ID, \Magento\Store\Model\ScopeInterface::SCOPE_STORE, $order->getStoreId() ) ); /* Add document text and number */ $this->insertDocumentNumber($page, __('Invoice # ') . $invoice->getIncrementId()); /* Add table */ $this->_drawHeader($page); /* Add body */ $items = $invoice->getAllItems(); $itemCount = 0; $maxItemsPerPage = 5; //Add Number of Products Displayed Per Page foreach ($items as $item) { if ($item->getOrderItem()->getParentItem()) { continue; } $this->_drawItem($item, $page, $order); $itemCount++; if ($itemCount % $maxItemsPerPage == 0 && $itemCount < count($items)) { $page = $this->newPage(); } } /* Add totals */ $this->insertTotals($page, $invoice); if ($invoice->getStoreId()) { $this->appEmulation->stopEnvironmentEmulation(); } } $this->_afterGetPdf(); return $pdf; } protected function insertOrder(&$page, $obj, $putOrderId = true) { if ($obj instanceof \Magento\Sales\Model\Order) { $shipment = null; $order = $obj; } elseif ($obj instanceof \Magento\Sales\Model\Order\Shipment) { $shipment = $obj; $order = $shipment->getOrder(); } $this->y = $this->y ? $this->y : 815; $top = $this->y; $page->setFillColor(new \Zend_Pdf_Color_GrayScale(0.45)); $page->setLineColor(new \Zend_Pdf_Color_GrayScale(0.45)); $page->drawRectangle(25, $top, 570, $top - 55); $page->setFillColor(new \Zend_Pdf_Color_GrayScale(1)); $this->setDocHeaderCoordinates([25, $top, 570, $top - 55]); $this->_setFontRegular($page, 10); if ($putOrderId) { $page->drawText(__('Order # ') . $order->getRealOrderId(), 35, $top -= 30, 'UTF-8'); $top +=15; } $top -=30; $page->drawText( __('Order Date: ') . $this->_localeDate->formatDate( $this->_localeDate->scopeDate( $order->getStore(), $order->getCreatedAt(), true ), \IntlDateFormatter::MEDIUM, false ), 35, $top, 'UTF-8' ); $top -= 10; $page->setFillColor(new \Zend_Pdf_Color_Rgb(0.93, 0.92, 0.92)); $page->setLineColor(new \Zend_Pdf_Color_GrayScale(0.5)); $page->setLineWidth(0.5); $page->drawRectangle(25, $top, 275, $top - 25); $page->drawRectangle(275, $top, 570, $top - 25); /* Calculate blocks info */ /* Billing Address */ $billingAddress = $this->_formatAddress($this->addressRenderer->format($order->getBillingAddress(), 'pdf')); /* Payment */ $paymentInfo = $this->_paymentData->getInfoBlock($order->getPayment())->setIsSecureMode(true)->toPdf(); $paymentInfo = $paymentInfo !== null ? htmlspecialchars_decode($paymentInfo, ENT_QUOTES) : ''; $payment = explode('{{pdf_row_separator}}', $paymentInfo); foreach ($payment as $key => $value) { if ($value && strip_tags(trim($value)) == '') { unset($payment[$key]); } } reset($payment); /* Shipping Address and Method */ if (!$order->getIsVirtual()) { /* Shipping Address */ $shippingAddress = $this->_formatAddress( $this->addressRenderer->format($order->getShippingAddress(), 'pdf') ); $shippingMethod = $order->getShippingDescription(); } $page->setFillColor(new \Zend_Pdf_Color_GrayScale(0)); $this->_setFontBold($page, 12); $page->drawText(__('Sold to:'), 35, $top - 15, 'UTF-8'); if (!$order->getIsVirtual()) { $page->drawText(__('Ship to:'), 285, $top - 15, 'UTF-8'); } else { $page->drawText(__('Payment Method:'), 285, $top - 15, 'UTF-8'); } $addressesHeight = $this->_calcAddressHeight($billingAddress); if (isset($shippingAddress)) { $addressesHeight = max($addressesHeight, $this->_calcAddressHeight($shippingAddress)); } $page->setFillColor(new \Zend_Pdf_Color_GrayScale(1)); $page->drawRectangle(25, $top - 25, 570, $top - 33 - $addressesHeight-15); $page->setFillColor(new \Zend_Pdf_Color_GrayScale(0)); $this->_setFontRegular($page, 10); $this->y = $top - 40; $addressesStartY = $this->y; foreach ($billingAddress as $value) { if ($value !== '') { $text = []; foreach ($this->string->split($value, 45, true, true) as $_value) { $text[] = $this->rtlTextHandler->reverseRtlText($_value); } foreach ($text as $part) { $page->drawText(strip_tags(ltrim($part ?: '')), 35, $this->y, 'UTF-8'); $this->y -= 15; } } } $addressesEndY = $this->y; if (!$order->getIsVirtual()) { $this->y = $addressesStartY; $shippingAddress = $shippingAddress ?? []; // @phpstan-ignore-line foreach ($shippingAddress as $value) { if ($value !== '') { $text = []; foreach ($this->string->split($value, 45, true, true) as $_value) { $text[] = $this->rtlTextHandler->reverseRtlText($_value); } foreach ($text as $part) { $page->drawText(strip_tags(ltrim($part ?: '')), 285, $this->y, 'UTF-8'); $this->y -= 15; } } } $addressesEndY = min($addressesEndY, $this->y); $this->y = $addressesEndY; $page->setFillColor(new \Zend_Pdf_Color_Rgb(0.93, 0.92, 0.92)); $page->setLineWidth(0.5); $page->drawRectangle(25, $this->y, 275, $this->y - 25); $page->drawRectangle(275, $this->y, 570, $this->y - 25); $this->y -= 15; $this->_setFontBold($page, 12); $page->setFillColor(new \Zend_Pdf_Color_GrayScale(0)); $page->drawText(__('Payment Method:'), 35, $this->y, 'UTF-8'); $page->drawText(__('Shipping Method:'), 285, $this->y, 'UTF-8'); $this->y -= 10; $page->setFillColor(new \Zend_Pdf_Color_GrayScale(1)); $this->_setFontRegular($page, 10); $page->setFillColor(new \Zend_Pdf_Color_GrayScale(0)); $paymentLeft = 35; $yPayments = $this->y - 15; } else { $yPayments = $addressesStartY; $paymentLeft = 285; } foreach ($payment as $value) { if ($value && trim($value) != '') { //Printing "Payment Method" lines $value = preg_replace('/<br[^>]*>/i', "\n", $value); foreach ($this->string->split($value, 45, true, true) as $_value) { $page->drawText(strip_tags(trim($_value ?: '')), $paymentLeft, $yPayments, 'UTF-8'); $yPayments -= 15; } } } if ($order->getIsVirtual()) { // replacement of Shipments-Payments rectangle block $yPayments = min($addressesEndY, $yPayments); $page->drawLine(25, $top - 25, 25, $yPayments); $page->drawLine(570, $top - 25, 570, $yPayments); $page->drawLine(25, $yPayments, 570, $yPayments); $this->y = $yPayments - 15; } else { $topMargin = 15; $methodStartY = $this->y; $this->y -= 15; if (isset($shippingMethod) && \is_string($shippingMethod)) { foreach ($this->string->split($shippingMethod, 45, true, true) as $_value) { $page->drawText(strip_tags(trim($_value ?: '')), 285, $this->y, 'UTF-8'); $this->y -= 15; } } $yShipments = $this->y; $totalShippingChargesText = "(" . __('Total Shipping Charges') . " " . $order->formatPriceTxt($order->getShippingAmount()) . ")"; $page->drawText($totalShippingChargesText, 285, $yShipments - $topMargin, 'UTF-8'); $yShipments -= $topMargin + 10; $tracks = []; if ($shipment) { $tracks = $shipment->getAllTracks(); } if (count($tracks)) { $page->setFillColor(new \Zend_Pdf_Color_Rgb(0.93, 0.92, 0.92)); $page->setLineWidth(0.5); $page->drawRectangle(285, $yShipments, 510, $yShipments - 10); $page->drawLine(400, $yShipments, 400, $yShipments - 10); //$page->drawLine(510, $yShipments, 510, $yShipments - 10); $this->_setFontRegular($page, 9); $page->setFillColor(new \Zend_Pdf_Color_GrayScale(0)); //$page->drawText(__('Carrier'), 290, $yShipments - 7 , 'UTF-8'); $page->drawText(__('Title'), 290, $yShipments - 7, 'UTF-8'); $page->drawText(__('Number'), 410, $yShipments - 7, 'UTF-8'); $yShipments -= 20; $this->_setFontRegular($page, 8); foreach ($tracks as $track) { $maxTitleLen = 45; $trackTitle = $track->getTitle() ?? ''; $endOfTitle = strlen($trackTitle) > $maxTitleLen ? '...' : ''; $truncatedTitle = substr($trackTitle, 0, $maxTitleLen) . $endOfTitle; $page->drawText($truncatedTitle, 292, $yShipments, 'UTF-8'); $page->drawText($track->getNumber(), 410, $yShipments, 'UTF-8'); $yShipments -= $topMargin - 5; } } else { $yShipments -= $topMargin - 5; } $currentY = min($yPayments, $yShipments); // replacement of Shipments-Payments rectangle block $page->drawLine(25, $methodStartY, 25, $currentY); //left $page->drawLine(25, $currentY, 570, $currentY); //bottom $page->drawLine(570, $currentY, 570, $methodStartY); //right $this->y = $currentY; $this->y -= 15; } } protected function _drawHeader(\Zend_Pdf_Page $page) { /* Add table head */ $this->_setFontRegular($page, 10); $page->setFillColor(new \Zend_Pdf_Color_Rgb(0.93, 0.92, 0.92)); $page->setLineColor(new \Zend_Pdf_Color_GrayScale(0.5)); $page->setLineWidth(0.5); $page->drawRectangle(25, $this->y, 570, $this->y - 15); $this->y -= 10; $page->setFillColor(new \Zend_Pdf_Color_Rgb(0, 0, 0)); //columns headers $lines[0][] = ['text' => __('Products'), 'feed' => 35]; $lines[0][] = ['text' => __('SKU'), 'feed' => 290, 'align' => 'right']; $lines[0][] = ['text' => __('Qty'), 'feed' => 435, 'align' => 'right']; $lines[0][] = ['text' => __('Price'), 'feed' => 360, 'align' => 'right']; $lines[0][] = ['text' => __('Tax'), 'feed' => 495, 'align' => 'right']; $lines[0][] = ['text' => __('Subtotal'), 'feed' => 565, 'align' => 'right']; $lineBlock = ['lines' => $lines, 'height' => 5]; $this->drawLineBlocks($page, [$lineBlock], ['table_header' => true]); $page->setFillColor(new \Zend_Pdf_Color_GrayScale(0)); $this->y -= 20; } }
Output –
Customizing Magento 2 invoice PDFs to add the number of products displayed per page can be a powerful way to enhance both customer experience and operational efficiency.
With this step-by-step guide, you can easily implement this feature, creating a more detailed and professional invoice for your Magento 2 store.
By following these steps and maintaining good coding practices, you can ensure that your invoice PDFs are as informative and user-friendly as possible.
Hello Magento Friends, In today’s blog, I will explain How to Add Tooltip in Checkout…
MongoDB is a popular NoSQL database that offers flexibility and scalability when handling modern web…
In NodeJS, callbacks empower developers to execute asynchronous operations like reading files, handling requests, and…
Hello Magento Friends, In today’s blog, we will learn How to Show SKU in Order…
The "Buy Now" and "Add to Cart" buttons serve as the primary call-to-action (CTA) elements…
Hello Magento Friends, In Magento 2, the checkout process allows customers to choose multiple shipping…