Генерация pdf-накладной к заказу

При работе сайтов в режиме b2b объемы заказов обычно имеют внушительные размеры - количество единиц номенклатуры в заказе может превышать десятки единиц.

Для таких вариантов достаточно распространенным решением является генерация pdf-файла с товарной накладной. 

В данном посте рассмотрим работу с модулем tcpdf на примере генерации накладной для заказа пользователя.

Библиотека tcpdf была выбрана, т.к в ней нет проблем при работе с битриксом, как это встречается у других библиотек (речь об mbstring.func_overload).

Итак, для начала качаем себе актуальную версию библиотеки. Распаковываем ее, например в папку /bitrix/php_interface/include/tcpdf. Теперь можно приступать к генерации файла.

Для начала можно познакомиться с примерами работы библиотеки TCPDF на официальном сайте. Также очень много есть неофициальных сайтов с примерами. Мне помог сайт: TCPDF практические заметки (документация) на русском.

Далее приступаем непосредственно к созданию файла. Приведу целиком весь код, с комментариями по тексту. В коде использован шрифт Arial, которого не было в поставке. Я нашел данный файл в сети уже сгенерированным и добавил к списку шрифтов.

define("STOP_STATISTICS", true);
define('NO_AGENT_CHECK', true);

use Bitrix\Sale;
use Bitrix\Sale\Order;

require($_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php');
require_once($_SERVER['DOCUMENT_ROOT'] . "/bitrix/php_interface/include/tcpdf/tcpdf.php");

// для модификации шапки и подвала сайта, необходимо переписать соответствующие функции.
// для этого, создадим свой класс унаследовав его от TCPDF:
	
class MdfPDF extends \TCPDF
{
	//Page header
	public function Header()
	{
		// Logo
		$headerdata = $this->getHeaderData();
		$this->Image($headerdata['logo'], 20, 10, $headerdata['logo_width'], '', 'JPG', '', 'T', false, 300, '', false, 
				false, 0, false, false, false);
		// Set font
		$this->SetFont('arial', '', 14);
		// Title
		/*
		// если нужно в верхнем колонтитуле вывести заголовок или какой-то текст, эти строки нужно раскомментировать 
		// и состыковать
		$this->Cell(0, 15, $headerdata['title'], 0, false, 'C', 0, '', 0, false, 'M', 'M'); 
		$this->Cell(0, 15, $headerdata['string'], 0, false, 'C', 0, '', 0, false, 'M', 'M');
		*/
		$this->SetTextColor(85, 85, 85); // цвет шрифта
		$html = "<br /><hr />"; // добавим линию, отделающую колонтитул от текста
		$this->writeHTML($html, true, false, true, false, '');
	}

	// Page footer
	public function Footer()
	{
		// Position at 15 mm from bottom
		$this->SetY(-15);
		// Set font
		$this->SetFont('arial', 'I', 8);
		// Page number
		$this->SetTextColor(85, 85, 85);
		$html = "<hr />"; 
		$this->writeHTML($html, true, false, true, false, '');
		$this->SetTextColor(0, 0, 0);
		$this->Cell(0, 10, 'Страница ' . $this->getAliasNumPage() . '/' . $this->getAliasNbPages(), 0, false, 'C', 0, 
				'', 0, false, 'T', 'M');
	}
}

class pdfit
{
	var $pdf;
	var $orderID;
	var $siteInfo;
	var $siteLogo;
	var $order;
	var $arUserData;

	function __construct($orderID, $siteLogo = false)
	{
		\Bitrix\Main\Loader::includeModule('sale');
		$this->orderID = intval($orderID); // идентификатор заказа для обработки
		$this->siteLogo = $siteLogo; // пусть к файлу с логотипом сайта
	}

	function process()
	{
		if ($this->orderID > 0)
		{
			$this->getOrder();
			if($this->checkOrderAccess()){
				$this->getSiteInfo();

				// определяем файл, в котором будем хранить сгенерированные накладные, если нужно
				// к стати, при хранении файлов, стоит также написать обработчик, который эти файлы будет удалять 
				// при удалении заказов
				$resultfile = $_SERVER['DOCUMENT_ROOT'] .'/upload/invoices/'. $this->siteInfo['SERVER_NAME'] . 
					'_invoice_' . $this->orderID . '.pdf';

				if(file_exists($resultfile)){
					// если файл уже был создан - просто выводим его
					header('Content-type: application/pdf');
					readfile($resultfile);
				} else {
					// иначе - проходим процедуру создания
					$this->getUserData();
					$this->initPdf();
					$this->getInvoiceReceiver();
					$this->GetHead();
					$this->GetOrderItems();
					$this->pdf->Output($resultfile, 'I'); // выводим на экран
					$this->pdf->Output($resultfile, 'F'); // сохраняем файл по указанному пути
				}
			} else {
				ShowError('Возникла ошибка генерации файла с накладной! Свяжитесь с администрацией сайта.');
			}
		}
	}

	function getOrder()
	{
		$this->order = Sale\Order::load($this->orderID);
	}

	function checkOrderAccess(){
		global $USER;
		global $APPLICATION;

		if(!$this->order) return false;

		if($USER->IsAdmin()) return true;
		if(!$USER->IsAuthorized()) return false;
		if($USER->GetID()==$this->order->getUserId()){
			return true;
		}
		if($APPLICATION->GetUserRight("sale") >= "W")
			return true; // оставляем менеджерам возможность просматривать накладные
		else {
			$APPLICATION->AuthForm("Доступ к заказу запрещён.");
			return false;
		}
	}

	function getSiteInfo($siteId = SITE_ID)
	{
		if (empty($siteId))
		{
			$siteId = "s1";
		}
		$arSite = false;
		$obCache = new \CPHPCache();
		if ($obCache->InitCache(36000, 'site_' . $siteId, '/'))
		{
			$arSite = $obCache->GetVars();
		} elseif ($obCache->StartDataCache())
		{
			$arSite = \CSite::GetByID($siteId)->Fetch();
			$obCache->EndDataCache($arSite);
		}
		$this->siteInfo = $arSite;
	}

	function initPdf()
	{
		$this->pdf = new \MdfPDF('P', 'mm', 'A4', true, 'UTF-8', false);
		$this->pdf->SetMargins(20, 20, 10);
		$this->pdf->SetAutoPageBreak(true, 20);
		if (is_array($this->siteInfo) && strlen($this->siteInfo['NAME']) > 0)
		{
			$this->pdf->SetAuthor($this->siteInfo['NAME']);
		}
		$this->setTitleByOrderID();
	}

	function getInvoiceDate()
	{
		$date = $this->order->getDateInsert()->getTimestamp();
		return 'от ' . FormatDate("j F Y", $date);
	}

	function getUserData()
	{
		$user_id = (int)$this->order->getUserId();
		$rsUser = CUser::GetByID($user_id);
		$this->arUserData = $rsUser->Fetch();
	}

	function getInvoiceReceiver()
	{
		$propertyCollection = $this->order->getPropertyCollection();
		$receiver = $propertyCollection->getPayerName()->getValue();
		if (!$receiver && !empty($this->arUserData))
		{
			$resline = [];

			if (strlen($this->arUserData['WORK_COMPANY']) > 0)
			{
				$resline[] = $this->arUserData['WORK_COMPANY'];
			}

			if (empty($resline))
			{
				if (strlen($this->arUserData['LAST_NAME']) > 0)
				{
					$resline[] = $this->arUserData['LAST_NAME'];
				}
				if (strlen($this->arUserData['NAME']) > 0)
				{
					$resline[] = $this->arUserData['NAME'];
				}
				if (strlen($this->arUserData['SECOND_NAME']) > 0)
				{
					$resline[] = $this->arUserData['SECOND_NAME'];
				}
			}

			if (empty($resline))
			{
				$resline[] = $this->arUserData['LOGIN'];
			}

			if (!empty($resline))
			{
				return implode(' ', $resline);
			}
		}

		return false;

	}

	function getInvoceContacts()
	{
		$phone = $this->order->getPropertyCollection()->getPhone();
		if ($phone) $phone = $phone->getValue;
		if (!$phone && !empty($this->arUserData))
		{
			if (strlen($this->arUserData['WORK_PHONE']) > 0)
			{
				$phone = $this->arUserData['WORK_PHONE'];
			} elseif (strlen($this->arUserData['PERSONAL_MOBILE']) > 0)
			{
				$phone = $this->arUserData['PERSONAL_MOBILE'];
			} elseif (strlen($this->arUserData['PERSONAL_PHONE']) > 0)
			{
				$phone = $this->arUserData['PERSONAL_PHONE'];
			} else
			{
				$phone = false;
			}
		} else $phone = false;

		$email = $this->order->getPropertyCollection()->getUserEmail();
		if ($email) $email = $email->getValue();

		if (!$email && !empty($this->arUserData))
		{
			if (strlen($this->arUserData['EMAIL']) > 0)
			{
				$email = $this->arUserData['EMAIL'];
			} else $email = false;
		} else $email = false;

		$address = $this->order->getPropertyCollection()->getAddress();
		if ($address) $address = $address->getValue();
		if (!$address) $address = false;

		return array(
			'phone' => $phone,
			'email' => $email,
			'address' => $address
		);

	}

	function setTitleByOrderID()
	{
		$title = 'Расходная накладная №' . $this->orderID;
		$this->pdf->SetTitle($title);
		$this->pdf->SetHeaderData($this->siteLogo, 30, $title, $this->getInvoiceDate());
	}

	function GetHead()
	{
		$this->pdf->AddPage();

		$headerdata = $this->pdf->getHeaderData();
		$this->pdf->SetFont('arial', '', 12);

		$this->pdf->Write(0, $headerdata['title'], '', 0, 'C', true,
			0, false, false, 0);
		$this->pdf->Write(0, $headerdata['string'], '', 0, 'C', true,
			0, false, false, 0);

		$this->pdf->SetFont('arial', '', 10);
		if ($receiver = $this->getInvoiceReceiver())
		{
			$this->pdf->Write(0, 'Покупатель: ' . $receiver, '', 0, 'L', true,
				0, false, false, 0);
		}

		$receiverContacts = $this->getInvoceContacts();
		if ($receiverContacts['phone'] && strlen($receiverContacts['phone']) > 0)
		{
			$this->pdf->Write(0, 'Тел: ' . $receiverContacts['phone'], '', 0, 'L', true,
				0, false, false, 0);
		}

		if ($receiverContacts['email'] && strlen($receiverContacts['email']) > 0)
		{
			$this->pdf->Write(0, 'E-mail: ' . $receiverContacts['email'], '', 0, 'L', true,
				0, false, false, 0);
		}

		if ($receiverContacts['address'] && strlen($receiverContacts['address']) > 0)
		{
			$this->pdf->Write(0, 'Адрес: ' . $receiverContacts['address'], '', 0, 'L', true,
				0, false, false, 0);
		}

		$this->pdf->Write(0, "", '', 0, 'C', true,
			0, false, false, 0);
	}

	function GetOrderItems()
	{
		$this->pdf->SetFillColor(51, 51, 51);
		$this->pdf->SetTextColor(255);
		$this->pdf->SetDrawColor(85, 85, 85);
		$this->pdf->SetLineWidth(0.3);
		$this->pdf->SetFont('arial', 'B');

		$coloumns = array(
			0 => ['t' => 'ID', 'w' => '20'],
			1 => ['t' => 'Товар', 'w' => '100'],
			2 => ['t' => 'Цена', 'w' => '20'],
			3 => ['t' => 'Кол-во', 'w' => '17'],
			4 => ['t' => 'Сумма', 'w' => '20'],
		);

		foreach ($coloumns as $arColoumn)
		{
			$this->pdf->Cell($arColoumn['w'], 7, $arColoumn['t'], 1, 0, 'C', 1);
		}
		$this->pdf->Ln();

		$this->pdf->SetFillColor(224, 235, 255);
		$this->pdf->SetTextColor(0);
		$this->pdf->SetFont('arial', '');

		$fill = 0;
		foreach ($this->order->getBasket()->getBasketItems() as $basketItem)
		{
			$this->pdf->Cell($coloumns[0]['w'], 6, $basketItem->getProductId(), 'LR', 0, 'C', $fill);
			$this->pdf->Cell($coloumns[1]['w'], 6, $basketItem->getField('NAME'), 'LR', 0, 'L', $fill);
			$this->pdf->Cell($coloumns[2]['w'], 6, CCurrencyLang::CurrencyFormat($basketItem->getPrice(), 'USD', true), 
				'LR', 0, 'L', $fill);
			$this->pdf->Cell($coloumns[3]['w'], 6, number_format($basketItem->getQuantity()), 'LR', 0, 'C', $fill);
			$this->pdf->Cell($coloumns[4]['w'], 6, CCurrencyLang::CurrencyFormat($basketItem->getFinalPrice(), 'USD', 
				true), 'LR', 0, 'L', $fill);
			$this->pdf->Ln();
			$fill = !$fill;
		}
		$this->pdf->Cell(array_reduce($coloumns, function (&$res, $item) {
			return $res + $item['w'];
		}, 0), 0, '', 'T');

		$this->pdf->Ln();
		$this->pdf->SetFont('arial', 'U',14);
		$this->pdf->Write(0, 'Товаров на ' . CCurrencyLang::CurrencyFormat($this->order->getBasket()->getPrice(), 
				'USD', true), '', 0, 'R', true,
			0, false, false, 0);

	}
}
$orderID = intval($_GET['order']);
if($orderID >0){
	$pdf = new pdfit($orderID, $_SERVER['DOCUMENT_ROOT'] . '/upload/sitelogo.jpg');
	$pdf->process();
} else {
	ShowError('Идентификатор заказа не указан!');
}

Таким образом, открывая страницу с данным скриптом, передавая в параметр order идентификатор заказ - получим накладную по выбранному заказу.

Рассмотрим теперь, что делает каждая из функций:

  • getOrder - загружает заказ по идентификатору;
  • checkOrderAccess - проверяет права пользователя на доступ к заказу. имеет смысл только в том случае, если и сама папка, где хранятся накладные, также закрыта от доступа;
  • getSiteInfo - получает информацию о текущем сайте из базы данных;
  • initPdf - инициирует объект pdf;
  • getInvoiceDate - выводит отформатированную строку с датой заказа;
  • getUserData - выводит информацию о пользователе, сделавшем заказ;
  • getInvoiceReceiver - выводит наименование получателя (или значение, указанное в свойствах заказа, если заполнено, или информацию из профиля пользователя, сделавшего заказ);
  • getInvoceContacts - контактная информация по заказу;
  • setTitleByOrderID - устанавливаем заголовок для файла;
  • GetHead - формируем шапку накладной;
  • GetOrderItems - самая ценная функция - выводит информацию о товарах.
Количество показов: 4407
04.10.2018

Возврат к списку

Если вам была полезна статья можете отблагодарить автора:
Ethereum:

0x16Df809287333C49D3A237296C6248A6c08702Bc

Разработка сайта

Подайте заявку на разработку сайта на базе готового решения от компании 1С-Битрикс или одного из партнеров компании. Максимально подробно опишите, чему будет посвящен сайт, если это интернет-магазин - что он будет продавать, нужна ли мультиязычность, будут ли разные типы цен (розница, опт, крупный опт), будет ли интеграция с 1С, будет ли выгрузка товаров на различные торговые площадки...

Сопровождение сайта

Вы можете подать заявку на сопровождение вашего сайта на базе 1С-Битрикс. Сопровождение включает в себя: проверка актуальности обновлений сайта, проверка актуальности резервной копии, консультации по сайту. Опишите в заявке, какие еще объемы планируются на сопровождении и на какой срок вы планируете заключить договор на сопровождение - мы подберем подходящий вам бюджет на сопровождение

Работы по сайту

Вы можете подать заявку на выполнение определенного объема работ по сайту. Опишите в заявке объем работ. Это может быть разработка какого-то нового функционала, доработки по имеющемуся функционалу, доработки под требования сео-специалистов. На основании заявки вам будет сформирован бюджет работ, а также названы сроки на выполнение тех или иных работ.