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

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

В этой заметке постараюсь описать процесс реализации индивидуальных цен, для пользователей. При этом, индивидуальные цены будут синхронизироваться с 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, плодить вызовы не надо

Блог-note: заметки разработчика

Повторить заказ на API 1С-Битрикс наполнив корзину из заказа

Задача: заполнить корзину пользователя теми же товарами и количествами которые были в ранее созданном заказе. При этом н...

Модифицировать состав заказа #ORDER_LIST# в почтовом событии "Новый заказ"

Если вы хотите как-то по особенному сверстать состав заказа в почтовом уведомлении 1С-Битрикс "Новый заказ", у вас это н...

Вывести артикул в печатную форму бланка заказа

Задача: в печатную форму бланка заказа добавить артикулы товаров и данные о пользователе.

Валидация пароля и подтверждения при регистрации в битрикс

Встала задачка сделать валидацию пароля и его подтверждения в стандартной форме регистрации сайта под управлением Битрик...

Вывести список всех пользователей с необходимой информацией

Если нужно, на какой-либо странице сайта, вывести всех пользователей из группы "Зарегистирированные пользователи", прост...

Удалить половину элементов инфоблока с помощью API

Задача: вот такая странная задача, нужно удалить из инфоблока половину элементов. Не важно каких, просто половину элемен...

Вывести новости из конкретного раздела инфоблока Битрикс

Бывает что требуется вывести конкретные новости или статьи из одного раздела инфоблока в Битрикс, для этого потребуется ...

Автоматически отгрузить заказ и сменить статус отгрузки, при выполнении заказа.

Проблема: Заказы, на сайте, обрабатываются только в 1С. При этом 1С не работает с отгрузками и обменивается только стату...

Заполнить инфоблок брендов из свойства инфоблока с товарами

Задача: в интернет-магазине есть инфоблок с товарами, загружаемый из 1С, у которого заполнено свойство "Производите...