Индивидуальные цены в 1С-Битрикс: в каталоге и при оформлении заказа

Просмотров: 8343

В этой заметке постараюсь описать процесс реализации индивидуальных цен, для пользователей. При этом, индивидуальные цены будут синхронизироваться с 1С:Предприятие - в обе стороны.

Индивидуальная цена распостраняется не на все товары каталога, а на какие-то определенные. И мы не будем создавать собственные компоненты и по чем зря раздувать init.php

Получаем цены, для контрагентов из 1С

Все данные будем хранить в Highload-блоке, что обеспечит нам и достаточную скорость работы (так как цен и контрагентов может быть много) и гибкость при дальнейшей работе.

Создаем Highload-блок, называем, например Индивидуальные цены контрагентов c тремя полями:

  • UF_XML_ID_CONTRAGENT в нем будем хранить XML-ID пользователя, этот же XML-ID используется и в 1С
  • UF_GOOD_BY_CONTRAGENT здесь храним XML-ID товара, на который у пользователя индивидуальная цена
  • UF_PRICE_CONTRAGENT а здесь, собственно, цена на этот товар для этого пользователя (тафталогия)

Заполняем тремя-четырмя элементами и экспортируем в файл. Этот файл отдаем программисту 1С, как образец и просим заполнить реальными данными.

Как получаем файл, выполняем импорт. В дальнейшем, 1С будет обмениваться этими данными автоматически. На стороне сайта ничего делать не надо, на стороне 1С используем дополнительный модуль обмена: который умеет выгружать справочники в HL блоки.

В итоге получаем заполненный инфоблок с индивидуальными ценами.

Индивидуальные цены в каталоге и при оформлении заказа в 1С-Битрикс

Выводим индивидуальную цену в каталоге

Теперь нужно вывести эти цены, авторизованному пользователю в разделе каталога и в детальной карточке товара.

В шаблоне catalog.item создаем файл result_modifier.php и добавляем в него следующий код (пояснения ниже):

if($USER->IsAuthorized()) {  
  $rsUser = CUser::GetByID($USER->GetID());
  $arUser = $rsUser->Fetch();
  $curUserXmlId = $arUser['XML_ID'];
  $curItemXmlId = $arResult['ITEM']['XML_ID'];

  \Bitrix\Main\Loader::includeModule('highloadblock');
  use Bitrix\Highloadblock as HL;
  use Bitrix\Main\Entity;

  $hlblockId = HL\HighloadBlockTable::getById(4)->fetch();
  $entity = HL\HighloadBlockTable::compileEntity($hlblockId);
  $entity_data_class = $entity->getDataClass();
  $rsDataPrice = $entity_data_class::getList(array(
      "select" => array("UF_PRICE_CONTRAGENT"),
      "filter" => array(
          "UF_GOOD_BY_CONTRAGENT" => $curItemXmlId,
          "UF_XML_ID_CONTRAGENT" => $curUserXmlId
      )
  ));
  while ($arItemPrice = $rsDataPrice->Fetch()) {
      $arResult['ITEM']['INDIVIDUAL_PRICE'] = $arItemPrice['UF_PRICE_CONTRAGENT'];
  }
}
  • Проверили авторизован ли пользователь, что бы почем зря не "напрягать" страницу, для не авторизованных.
  • В переменную curUserXmlId загнали XML-ID текущего пользователя. В curItemXmlId - XML-ID текущего товара
  • Далее: подключили модуль Highloadblock и получили из него индивидуальную цену "select" => array("UF_PRICE_CONTRAGENT"), отфильтровав ее, зная запись с XML-ID пользователя и товара.
  • Добавили в массив $arResult['ITEM'] эту цену, на этот товар, для этого пользователя

В шаблоне catalog.item/ВАШ_ШАБЛОН/card/template.php просто делаем условие, обвернув в него цену. Если $arResult['ITEM']['INDIVIDUAL_PRICE'] пустая- то выводим обычную цену, иначе показываем индивидуальную.

foreach ($item['ITEM_PRICES'] as $PRICE) {
  if (empty($arResult['ITEM']['INDIVIDUAL_PRICE'])) {
      echo $PRICE['PRINT_RATIO_BASE_PRICE'];
  } else {
      echo $arResult['ITEM']['INDIVIDUAL_PRICE'] . ' руб.';
  }
}

По сути, тоже самое проделываем с детальной карточкой товара в result_modifier.php и template.php шаблона catalog.element:

//// ------- result_modifier.php
if($USER->IsAuthorized()) {  
  $rsUser = CUser::GetByID($USER->GetID());
  $arUser = $rsUser->Fetch();
  $curUserXmlId = $arUser['XML_ID'];
  $curItemXmlID = $arResult['XML_ID'];

  \Bitrix\Main\Loader::includeModule('highloadblock');
  use Bitrix\Highloadblock as HL;
  use Bitrix\Main\Entity;

  $hlblockId = HL\HighloadBlockTable::getById(4)->fetch();
  $entity = HL\HighloadBlockTable::compileEntity($hlblockId);
  $entity_data_class = $entity->getDataClass();
  $rsDataPrice = $entity_data_class::getList(array(
      "select" => array("UF_PRICE_CONTRAGENT"),
      "filter" => array(
          "UF_GOOD_BY_CONTRAGENT" => $curItemXmlID,
          "UF_XML_ID_CONTRAGENT" => $curUserXmlId
      )
  ));
  while ($arItemPrice = $rsDataPrice->Fetch()) {
      $arResult['INDIVIDUAL_PRICE'] = $arItemPrice['UF_PRICE_CONTRAGENT'];
  }
}

//// ------- template.php
foreach ($$arResult['ITEM_PRICES'] as $PRICE) {
  if (empty($arResult['INDIVIDUAL_PRICE'])) {
      echo $PRICE['PRINT_RATIO_BASE_PRICE'];
  } else {
      echo $arResult['INDIVIDUAL_PRICE'] . ' руб.';
  }
}

Выводим индивидуальную цену в корзине

В целом, в корзине проделываем тоже самое, единственное что весь код добавляем в файл /sale.basket.basket/ВАШ_ШАБЛОН/mutator.php - здесь нужно модифицировать несколько полей массива, в которых содержатся цены и суммы каждого товара.

До foreach ($this->basketItems as $row):

\Bitrix\Main\Loader::includeModule('highloadblock');
global $USER;
$rsUser = CUser::GetByID($USER->GetID());
$arUser = $rsUser->Fetch();
$curUserXmlId = $arUser['XML_ID']; // XML ID текущего пользователя
use Bitrix\Highloadblock as HL;
use Bitrix\Main\Entity;
$hlblockId = HL\HighloadBlockTable::getById(4)->fetch(); // Получаем запись из HL блока №4
$entity = HL\HighloadBlockTable::compileEntity($hlblockId);
$entity_data_class = $entity->getDataClass();

В самом переборе массива, в самом верху, так же проверяем наличие индивидуальной цены для этого товара, этому пользователю и подменяем значения ключей массива:

$rsDataPrice = $entity_data_class::getList(array(
  "select" => array("UF_PRICE_CONTRAGENT"),
    "filter" => array(
        "UF_GOOD_BY_CONTRAGENT" => $row['PRODUCT_XML_ID'],
        "UF_XML_ID_CONTRAGENT" => $curUserXmlId
    )
));
while ($arItemPrice = $rsDataPrice->Fetch()) {
    $curItemPrice = $arItemPrice['UF_PRICE_CONTRAGENT']; // Индивидуальная цена для этого товара этому контрагенту
}
if (!empty($curItemPrice)) {
    $row['PRICE_FORMATED'] = $curItemPrice . ' руб.';
  $row['PRICE'] = $curItemPrice;
  $row['FULL_PRICE'] = $curItemPrice;
  $row['FULL_PRICE_FORMATED'] = $curItemPrice . $row['CURRENCY'];
  $row['SUM_PRICE'] = $curItemPrice * $row['QUANTITY'];
  $row['SUM_PRICE_FORMATED'] = $curItemPrice * $row['QUANTITY'] . ' руб.';
  $row['SUM'] = $curItemPrice * $row['QUANTITY'] . ' руб.';
  $row['SUM_FULL_PRICE_FORMATED'] = $curItemPrice * $row['QUANTITY'] . ' руб.';
  $row['SUM_FULL_PRICE'] = $curItemPrice * $row['QUANTITY'];
  $row['SUM_FULL_PRICE'] = $curItemPrice * $row['QUANTITY'];
  $row['SUM_VALUE'] = $curItemPrice * $row['QUANTITY'];
}

И в самом низу, перед завершением foreach ($this->basketItems as $row)

$allSumCustom += $row['PRICE'] * $row['QUANTITY'];

// И меняем сумму корзины в totalData
$totalData = array(
    'DISABLE_CHECKOUT' => (int)$result['ORDERABLE_BASKET_ITEMS_COUNT'] === 0,
    'PRICE' => $allSumCustom,
    'PRICE_FORMATED' => $allSumCustom . ' руб',
    'PRICE_WITHOUT_DISCOUNT_FORMATED' => $result['PRICE_WITHOUT_DISCOUNT'],
    'CURRENCY' => $result['CURRENCY']
);

При желании, также подменяем сумму в шаблоне sale.basket.basket.line - таким же образом, как в шаблонах каталога, перемножив и сплюсовав цены и колличества товаров.

Оформление заказа с индивидуальными ценами

А теперь самый главный финт - нужно сохранить индивидуальные цены в заказ. Будем делать это методом CSaleBasket::Update - который обновляет записи корзины, связанной с заказом.

То есть: само оформление произойдет с обычными ценами, но на последнем шаге, когда заказ уже сформирован, просто получаем его ID и меняем цены на индивидуальные.

В файл /sale.order.ajax/ВАШ_ШАБЛОН/confirm.php добавляем код.
До проверки if (!empty($arResult["ORDER"]))::

$rsUser = CUser::GetByID($USER->GetID());
$arUser = $rsUser->Fetch();
$userId = $arUser['ID'];
$curUserXmlId = $arUser['XML_ID'];
\Bitrix\Main\Loader::includeModule('highloadblock');

use Bitrix\Highloadblock as HL;
use Bitrix\Main\Entity;

$hlblockId = HL\HighloadBlockTable::getById(4)->fetch(); // Получаем запись из HL блока №4
$entity = HL\HighloadBlockTable::compileEntity($hlblockId);
$entity_data_class = $entity->getDataClass();

Внутри if (!empty($arResult["ORDER"])):

$dbBasketItems = CSaleBasket::GetList(
    false,
    array(
        "ORDER_ID" => $arResult["ORDER"]['ID']
    ),
    false,
    false,
    array('PRODUCT_XML_ID', 'ID')
    );
    while ($arItems = $dbBasketItems->Fetch()) {
        $rsDataPrice = $entity_data_class::getList(array(
            "select" => array("UF_PRICE_CONTRAGENT"),
            "filter" => array(
                "UF_GOOD_BY_CONTRAGENT" => $arItems['PRODUCT_XML_ID'],
                "UF_XML_ID_CONTRAGENT" => $curUserXmlId
            )
        ));
        while ($arItemPrice = $rsDataPrice->Fetch()) {
            $curItemPrice = $arItemPrice['UF_PRICE_CONTRAGENT'];
        }
        if (!empty($curItemPrice)) {
            $arFields = array(
                "PRICE" => $curItemPrice,
            );
            CSaleBasket::Update($arItems['ID'], $arFields);
        }
        unset($curItemPrice);
    }
  • Методом CSaleBasket::GetList получили корзину (все товары), связанную с созданным заказом. От этого метода получили PRODUCT_XML_ID и ID. Обратите внимание, ID это не ID товара в каталоге, а ID записи в корзине.
  • Все тем же способом, что пользовались выше, проверили наличие индивидуальной цены у каждого товара и, при наличии оной меняем цену в записи методом CSaleBasket::Update.

Все. Заказ будет формироваться с учетом индивидуальной цены, в 1С этот заказ придет с уже измененными данными - в общем, задача решена.

PS: В одной из следующих заметок расскажу о способе подмены цен в корзине, с помощью провайдера цен. При нем, не нужно будет ничего делать с шаблоном корзины и оформления заказа.
Но, если индивидуальных цен много - Провайдер цен может вызвать значительные нагрузки. Потому, оставил это на будущие заметки.
Способ описанный выше, хоть и более обширный, но с другой стороны: сделал и забыл. Не так часто меняется логика корзины и оформления.

Михаил Базаров 05.12.2020
Не учел, в заметке, один момент:
Пересчет заказа происходит после его окончательного оформления.
А в процессе оформления, цены и сумма будут без учета индивидуальных.

Не учел это, так как на конкретном проекте максимально упростил оформление заказа, у меня там не показываются товары и сумма (пример на скрине)

Если эти данные нужны, то также модифицируете их через result_modifier.php редактируя массив
$arResult['BASKET_ITEMS'] самого компонента оформления.
PS: Его же используете в confirm.php, плодить вызовы не надо