Разработка кастомной PIM-системы для 1С-Битрикс
Коммерческие PIM-системы (Akeneo, Pimcore, Salsify) решают задачу управления контентом, но создают зависимость от стороннего продукта и добавляют сложность в архитектуру. Кастомная PIM на базе Битрикс — это когда Битрикс сам становится мастер-системой для продуктовых данных: с кастомным административным интерфейсом, логикой валидации атрибутов и возможностью синдикации на несколько каналов.
Когда нужна кастомная PIM
Кастомная PIM оправдана, когда:
- Команда работает только в экосистеме Битрикс и не хочет учить новую систему
- Бюджет не позволяет лицензию Akeneo Enterprise ($25 000+/год)
- Требования специфичны настолько, что готовая PIM всё равно требует обширной кастомизации
- Каталог до 50 000–100 000 SKU (для большего объёма стоит рассмотреть специализированный PIM)
Архитектура данных
Стандартный инфоблок Битрикс хранит свойства в b_iblock_element_property по модели EAV. При большом количестве атрибутов (50+) и высоком трафике это создаёт проблемы производительности. Для кастомной PIM используем гибридный подход:
Фиксированные атрибуты (название, описание, изображения) — в полях инфоблока и свойствах с MULTIPLE = 'N'.
Динамические атрибуты (специфичные для категории: для телефонов — RAM/ROM, для одежды — состав ткани) — в кастомных таблицах:
CREATE TABLE b_pim_attribute (
ID INT AUTO_INCREMENT PRIMARY KEY,
IBLOCK_SECTION_ID INT, -- к какой категории привязан атрибут
CODE VARCHAR(100) NOT NULL,
NAME VARCHAR(255) NOT NULL,
TYPE ENUM('string','number','boolean','list','multilist') DEFAULT 'string',
IS_REQUIRED TINYINT(1) DEFAULT 0,
IS_FILTERABLE TINYINT(1) DEFAULT 0,
SORT INT DEFAULT 100,
INDEX idx_section (IBLOCK_SECTION_ID)
);
CREATE TABLE b_pim_attribute_value (
ID INT AUTO_INCREMENT PRIMARY KEY,
PRODUCT_ID INT NOT NULL, -- ID элемента инфоблока
ATTRIBUTE_ID INT NOT NULL,
VALUE_STRING VARCHAR(1000),
VALUE_NUMBER DECIMAL(15,4),
VALUE_BOOLEAN TINYINT(1),
INDEX idx_product_attr (PRODUCT_ID, ATTRIBUTE_ID)
);
CREATE TABLE b_pim_attribute_option (
ID INT AUTO_INCREMENT PRIMARY KEY,
ATTRIBUTE_ID INT NOT NULL,
VALUE VARCHAR(500) NOT NULL,
SORT INT DEFAULT 100
);
Такой подход позволяет задавать атрибуты на уровне категории без изменения схемы инфоблока.
Административный интерфейс редактирования атрибутов
Кастомный административный раздел на базе компонента bitrix:main.ui.grid:
// /local/modules/company.pim/admin/attributes.php
require_once $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_admin_before.php';
$APPLICATION->SetTitle('PIM: Управление атрибутами');
// Список атрибутов для выбранной категории
$sectionId = (int)$_GET['section_id'];
$connection = \Bitrix\Main\Application::getConnection();
$attributes = $connection->query(
"SELECT * FROM b_pim_attribute WHERE IBLOCK_SECTION_ID = {$sectionId} ORDER BY SORT"
);
Интерфейс позволяет менеджеру:
- Добавлять атрибуты к категории (без изменения структуры БД)
- Задавать тип, обязательность, возможность фильтрации
- Сортировать атрибуты перетаскиванием
- Копировать набор атрибутов в дочернюю категорию
Форма редактирования товара с динамическими атрибутами
В административном редакторе товара (sale.admin.order.edit или кастомная страница) нужно отображать и сохранять атрибуты текущей категории:
function renderPimFields(int $productId, int $sectionId): string
{
$connection = \Bitrix\Main\Application::getConnection();
$attributes = $connection->query(
"SELECT a.*, v.VALUE_STRING, v.VALUE_NUMBER, v.VALUE_BOOLEAN
FROM b_pim_attribute a
LEFT JOIN b_pim_attribute_value v
ON v.ATTRIBUTE_ID = a.ID AND v.PRODUCT_ID = {$productId}
WHERE a.IBLOCK_SECTION_ID = {$sectionId}
ORDER BY a.SORT"
);
$html = '<table class="pim-attributes">';
while ($attr = $attributes->fetch()) {
$html .= '<tr>';
$html .= '<td><label>' . htmlspecialchars($attr['NAME'])
. ($attr['IS_REQUIRED'] ? ' <span class="req">*</span>' : '')
. '</label></td>';
$html .= '<td>' . renderAttributeInput($attr) . '</td>';
$html .= '</tr>';
}
$html .= '</table>';
return $html;
}
function savePimFields(int $productId, array $postData): void
{
$connection = \Bitrix\Main\Application::getConnection();
foreach ($postData as $attrId => $value) {
$attrId = (int)$attrId;
$existing = $connection->query(
"SELECT ID FROM b_pim_attribute_value
WHERE PRODUCT_ID = {$productId} AND ATTRIBUTE_ID = {$attrId}"
)->fetch();
$attr = $connection->query(
"SELECT TYPE FROM b_pim_attribute WHERE ID = {$attrId}"
)->fetch();
$field = match($attr['TYPE']) {
'number' => 'VALUE_NUMBER',
'boolean' => 'VALUE_BOOLEAN',
default => 'VALUE_STRING',
};
$safeVal = $connection->getSqlHelper()->forSql($value);
if ($existing) {
$connection->query(
"UPDATE b_pim_attribute_value
SET {$field} = '{$safeVal}'
WHERE ID = {$existing['ID']}"
);
} else {
$connection->query(
"INSERT INTO b_pim_attribute_value
(PRODUCT_ID, ATTRIBUTE_ID, {$field})
VALUES ({$productId}, {$attrId}, '{$safeVal}')"
);
}
}
}
Валидация атрибутов
Обязательные атрибуты проверяются при сохранении товара:
function validatePimAttributes(int $productId, int $sectionId): array
{
$errors = [];
$connection = \Bitrix\Main\Application::getConnection();
$required = $connection->query(
"SELECT a.ID, a.NAME FROM b_pim_attribute a
LEFT JOIN b_pim_attribute_value v
ON v.ATTRIBUTE_ID = a.ID AND v.PRODUCT_ID = {$productId}
WHERE a.IBLOCK_SECTION_ID = {$sectionId}
AND a.IS_REQUIRED = 1
AND v.ID IS NULL"
);
while ($row = $required->fetch()) {
$errors[] = 'Не заполнен обязательный атрибут: ' . $row['NAME'];
}
return $errors;
}
Синдикация на несколько каналов
Кастомная PIM позволяет экспортировать данные в разные форматы для разных каналов:
-
Yandex.Market YML — генератор фида читает
b_pim_attribute_valueи маппит в стандартные элементы YML - OZON / Wildberries — JSON-шаблоны под каждый маркетплейс
-
Google Merchant — XML-фид с атрибутами
g:brand,g:gtinи т.д.
function buildYmlOffer(int $productId): array
{
$connection = \Bitrix\Main\Application::getConnection();
$values = $connection->query(
"SELECT a.CODE, v.VALUE_STRING, v.VALUE_NUMBER
FROM b_pim_attribute_value v
JOIN b_pim_attribute a ON a.ID = v.ATTRIBUTE_ID
WHERE v.PRODUCT_ID = {$productId}"
);
$params = [];
while ($row = $values->fetch()) {
$params[$row['CODE']] = $row['VALUE_STRING'] ?? $row['VALUE_NUMBER'];
}
return $params; // используется при генерации YML
}
Сроки реализации
| Объём | Состав | Срок |
|---|---|---|
| Базовая PIM (динамические атрибуты, форма редактирования) | Таблицы + форма + сохранение | 2–3 недели |
| PIM с валидацией, UI сортировки, копированием атрибутов | Полноценный admin UI + JS-сортировка | 4–5 недель |
| PIM + синдикация (YML, маркетплейсы) + массовое редактирование | Экспортёры + bulk edit таблица | 6–8 недель |
Кастомная PIM — правильный выбор для команд, уже работающих в Битрикс. Главный риск — чрезмерное усложнение: нужно чётко зафиксировать требования до начала разработки и не пытаться воспроизвести Akeneo внутри Битрикс.







