Отправка счетов и накладных вместе с e-mail уведомлением о заказе

Довольно часто покупатели интернет-магазина являются юридическими лицами и просят выслать им счет на оплату заказа. Почему бы не формировать счет автоматически и отсылать его вместе с письмом-подтверждением заказа? Для b2b сайта издательства «Росмэн» мы реализовали данный функционал.
[spoiler]
Идея

Будем формировать файл счета после создания заказа и перед отправкой письма-подтверждения, на основе xls-шаблона используя PHPExcel. При отправке письма-подтверждения о заказе — будем прикреплять файл счета к письму.

Реализация

1. Создадим шаблоны необходимых документов (счетов, актов, накладных и т.п.) в формате Excel — внесем в них все нужные параметры в виде "заглушек", в них будут выводиться данные конкретного заказа — номер заказа, секция магазина, дата, менеджер заказа, список товаров, сумма и т.п.:
edc30de325139cd4631ad069dec3ea1b.png

2. В файле /bitrix/php_interface/init.php создадим обработчик события OnBeforeEventAddHandler - для того, чтобы отправить письмо с аттачами (за идею, как в "Битриксе" реализовать отправку письма с аттачем, спасибо Антону Долганину):
function OnBeforeEventAddHandler($event, $lid, $arFields) {
   if ($event == "SALE_NEW_ORDER") {
      include("make_xls.php");
       $arXLSfiles = scandir(dirname(__FILE__).'/orders/'.$arOrder['ID']);
      include("mail_attach.php");
      SendAttache($event, $lid, $arFields, $arXLSfiles);
      return false;
   }
}
Если почтовое событие - SALE_NEW_ORDER (новый заказ в магазине), то подключаем код make_xls.php (см. пункт 3) - для создания Excel-файлов (подставляя данные заказа в xls-шаблон). В качестве аттачей - берем все xls-файлы, которые сформированы после выполнения файла make_xls.php в папке /orders/'.$arOrder['ID']/; mail_attach.php - содержит функцию отправки письма с аттачем SendAttache.

return false - необходимо для блокировки отправки письма "Битриксом", т.к. иначе будут проходить два письма - одно отправленное функцией SendAttache (с аттачами), второе - отправленное "Битриксом" (без аттачей)

3. Файл make_xls.php - создание Excel-файлов. Полный код приводить здесь не буду, основные моменты:

Собираем данные о заказе, пользователе, скидке заказа, корзине и т.п.:
if($arFields['ORDER_ID']) {

   //Заказ:
   $arOrder = CSaleOrder::GetByID($arFields['ORDER_ID']);

   //Пользователь:
   global $USER;
   $rsUser = CUser::GetList(($by), ($order), array("ID"=>$USER->GetID()), array("SELECT" => array("ADMIN_NOTES","UF_*")));
   $arUser = $rsUser->GetNext();

   //Менеджер пользователя:
   $rsManager = CUserFieldEnum::GetList(array(), array("ID" => $arUser["UF_MANAGER"]));
   $arManager = $rsManager->GetNext();
   
   //Скидки пользователя:
   foreach($arUser as $key=>$value) {
      ...
   }
   
   //Корзина:
   $dbBasketItems = CSaleBasket::GetList(array("NAME" => "ASC"), array("ORDER_ID" => $arFields["ORDER_ID"]), false, false, array("ID", "PRODUCT_ID", "QUANTITY", "PRICE", "NAME")); 
   while ($arBasketItem = $dbBasketItems->GetNext()) {
      ...
   }
}
Формируем xls-файл:
include_once dirname(__FILE__) . '/PHPExcel.php';
include_once dirname(__FILE__) . '/PHPExcel.addon.php';

mkdir(dirname(__FILE__).'/orders/'.$arOrder['ID'], 0755, true);

PHPExcelAddon::convert(dirname(__FILE__).'/order_template.xls',array (
   'order' => "Заказ №".$arOrder['ID'],
   'section' => $arSectionGroupsNames[$sgid],
   'datetime' =>  $arOrder['DATE_INSERT'],
   'client_name' => $arUser['NAME'].' '.$arUser['LAST_NAME'],
   'manager_name' => $arManager['VALUE'],
   'client_email' => $arUser['EMAIL'],
   'manager_email' => $arManager['XML_ID'],
   'basket.articles_ADDROWS' => $arBasketArticles,
   'basket.names' => $arBasketNames,
   'basket.quantities' => $arBasketQuantities,
   'basket.summs' => $arBasketPrices,
   'basket.discounts' => $arBasketDiscounts,
   'total_summ' => $total_summ,
),'orders/'.$arOrder['ID'].'/Заказ_'.$arOrder['ID'].'_'.$arSectionGroupsNames[$sgid]);
PHPExcel.addon.php - идея взята из статьи на habrahabr:
class PHPExcelAddon {
   public function __construct () {}
   public static function convert ($file, $data=array (), $filename='file') {
      $objPHPExcel = PHPExcel_IOFactory::load( $file );
      $objPHPExcel->setActiveSheetIndex(0);
      $aSheet = $objPHPExcel->getActiveSheet();
      foreach($aSheet->getRowIterator() as $row){
         ...
      }
      $objWriter = new PHPExcel_Writer_Excel2007($objPHPExcel);
      $objWriter->save(dirname($file).'/'.$filename.'.xlsx');
      $objPHPExcel->disconnectWorksheets();
      unset($objPHPExcel);
   }
}
 
В результате, получаем письма-уведомления, к которым сразу же прикреплены необходимые клиенту файлы документов его заказа.

PS: Как вариант, можно формировать документы для клиентов в pdf, используя html-формы документов, доступные в интерфейсе управления заказами (или создавая свои):
d3efc19a310fae473f5b83737fb3aa5b.png

Но это уже другая история. :)
0
16.01.2025 18:55:37
0
16.01.2025 19:05:25
0
16.01.2025 19:05:31
0
16.01.2025 19:05:31
-1 OR 2+219-219-1=0+0+0+1 --
0
16.01.2025 19:05:32
-1 OR 3+219-219-1=0+0+0+1 --
0
16.01.2025 19:05:32
-1 OR 2+872-872-1=0+0+0+1
0
16.01.2025 19:05:32
-1 OR 3+872-872-1=0+0+0+1
0
16.01.2025 19:05:32
-1' OR 2+594-594-1=0+0+0+1 --
0
16.01.2025 19:05:32
-1' OR 3+594-594-1=0+0+0+1 --
0
16.01.2025 19:05:32
-1' OR 2+369-369-1=0+0+0+1 or 'BW7upAWV'='
0
16.01.2025 19:05:33
-1' OR 3+369-369-1=0+0+0+1 or 'BW7upAWV'='
0
16.01.2025 19:05:33
-1" OR 2+179-179-1=0+0+0+1 --
0
16.01.2025 19:05:34
-1" OR 3+179-179-1=0+0+0+1 --
0
16.01.2025 19:05:43
if(now()=sysdate(),sleep(15),0)
0
16.01.2025 19:05:51
e0'XOR(if(now()=sysdate(),sleep(15),0))XOR'Z
0
16.01.2025 19:06:00
e0"XOR(if(now()=sysdate(),sleep(15),0))XOR"Z
0
16.01.2025 19:06:15
(sel ect(0)from(select(sleep(15)))v)/*'+(select(0)fr om(sel ect(sleep(15)))v)+'"+(select(0)fr om(select(sleep(15)))v)+"*/
0
16.01.2025 19:06:31
e-1 waitfor delay '0:0:15' --
0
16.01.2025 19:06:40
eSLEUN94Y'; waitfor delay '0:0:15' --
0
16.01.2025 19:07:09
eO63mTogp') OR 749=(SEL ECT 749 FR OM PG_SLEEP(15))--
0
16.01.2025 19:07:20
ex3SUj0Dd')) OR 937=(SEL ECT 937 FR OM PG_SLEEP(15))--
0
16.01.2025 19:07:28
e'||DBMS_PIPE.RECEIVE_MESSAGE(CHR(98)||CHR(98)||CHR(98),15)||'
0
16.01.2025 19:07:29
0
16.01.2025 19:07:29
0
16.01.2025 19:07:30
e????%2527%2522\'\"
0
16.01.2025 19:07:30
0
16.01.2025 19:07:49