Інтеграція 1С-Бітрікс із PIM-системою Akeneo
Akeneo — спеціалізована система управління інформацією про продукти (PIM). Коли в каталозі тисячі товарів з десятками атрибутів, перекладами кількома мовами та варіантами для різних каналів, зберігання всього цього безпосередньо в інфоблоці Бітрікс стає незручним. Akeneo займається збагаченням і валідацією даних, Бітрікс — продажами та трансакціями. Інтеграція будується на REST API Akeneo.
Архітектура потоку даних
Типовий потік:
1С (номенклатура, ціни, залишки)
↓
Akeneo
(описи, атрибути, медіа, переклади)
↓
Бітрікс
(каталог, кошик, замовлення)
Akeneo є "господарем" контенту продуктів. 1С — джерелом для SKU, цін та залишків. Бітрікс — каналом продажу, який читає дані з Akeneo.
Напрямок синхронізації: Akeneo → Бітрікс. Зворотний потік (Бітрікс → Akeneo) використовується рідко — лише для передачі статистики (перегляди, замовлення) якщо необхідно.
REST API Akeneo
Akeneo надає REST API з OAuth 2.0 (client credentials):
POST /api/oauth/v1/token
GET /api/rest/v1/products?search={"enabled":[{"operator":"=","value":true}]}
GET /api/rest/v1/products/{code}
GET /api/rest/v1/product-models
GET /api/rest/v1/categories
GET /api/rest/v1/attributes
GET /api/rest/v1/media-files/{code}/download
Отримання токена:
class AkeneoClient
{
private string $baseUrl;
private string $token;
public function __construct(
string $baseUrl,
string $clientId,
string $secret,
string $username,
string $password
) {
$this->baseUrl = rtrim($baseUrl, '/');
$this->token = $this->authenticate($clientId, $secret, $username, $password);
}
private function authenticate(
string $clientId,
string $secret,
string $username,
string $password
): string {
$http = new \Bitrix\Main\Web\HttpClient();
$http->setHeader('Authorization',
'Basic ' . base64_encode($clientId . ':' . $secret));
$http->setHeader('Content-Type', 'application/json');
$response = $http->post(
$this->baseUrl . '/api/oauth/v1/token',
json_encode([
'grant_type' => 'password',
'username' => $username,
'password' => $password,
])
);
$data = json_decode($response, true);
return $data['access_token'] ?? throw new \RuntimeException('Akeneo auth failed');
}
public function getProducts(int $page = 1, int $limit = 100): array
{
$http = new \Bitrix\Main\Web\HttpClient();
$http->setHeader('Authorization', 'Bearer ' . $this->token);
$response = $http->get(
$this->baseUrl . '/api/rest/v1/products'
. '?page=' . $page . '&limit=' . $limit
. '&with_attribute_options=true'
);
return json_decode($response, true)['_embedded']['items'] ?? [];
}
public function getMediaFile(string $code): string
{
$http = new \Bitrix\Main\Web\HttpClient();
$http->setHeader('Authorization', 'Bearer ' . $this->token);
return $http->get($this->baseUrl . '/api/rest/v1/media-files/' . $code . '/download');
}
}
Маппінг атрибутів Akeneo → Бітрікс
Кожний атрибут Akeneo повинен бути зіставлений зі властивістю інфоблоку Бітрікс. Маппінг зберігається у файлі конфігурації або HL-блоці:
// /local/config/akeneo-mapping.php
return [
// 'akeneo_attribute_code' => 'BITRIX_PROPERTY_CODE'
'description' => 'DETAIL_TEXT', // Спеціальне поле
'short_description' => 'PREVIEW_TEXT',
'brand' => 'BRAND',
'weight' => 'WEIGHT',
'color' => 'COLOR',
'material' => 'MATERIAL',
'care_instructions' => 'CARE',
'country_of_origin' => 'COUNTRY_ORIGIN',
];
Атрибути Akeneo можуть бути локалізовані (різні значення для різних локалей). Маппінг локалей:
$localeMap = [
'ru_RU' => 'ru',
'en_US' => 'en',
'de_DE' => 'de',
];
Синхронізація: логіка агента
Агент Бітрікс запускає синхронізацію за розкладом. Для 50,000+ товарів — інкрементальний режим (лише змінені з моменту останньої синхронізації):
function syncAkeneoProductsAgent(): string
{
$lastSync = \Bitrix\Main\Config\Option::get('akeneo_sync', 'last_sync', '');
$client = new AkeneoClient(
AKENEO_URL, AKENEO_CLIENT_ID, AKENEO_SECRET,
AKENEO_USER, AKENEO_PASSWORD
);
$mapping = include '/local/config/akeneo-mapping.php';
$syncTime = date('c');
$page = 1;
do {
$products = $client->getProducts($page, 100);
foreach ($products as $akeneoProduct) {
syncSingleProduct($akeneoProduct, $mapping, $client);
}
$page++;
} while (count($products) === 100);
\Bitrix\Main\Config\Option::set('akeneo_sync', 'last_sync', $syncTime);
return __FUNCTION__ . '();';
}
function syncSingleProduct(array $product, array $mapping, AkeneoClient $client): void
{
$sku = $product['identifier']; // Код товара в Akeneo = артикул
$enabled = $product['enabled'];
// Шукаємо товар за артикулом
$existing = CIBlockElement::GetList(
[],
['IBLOCK_ID' => CATALOG_IBLOCK_ID, 'PROPERTY_CML2_ARTICLE' => $sku]
)->Fetch();
$el = new CIBlockElement();
$fields = [
'IBLOCK_ID' => CATALOG_IBLOCK_ID,
'ACTIVE' => $enabled ? 'Y' : 'N',
'NAME' => getAkeneoValue($product['values']['name'] ?? [], 'ru_RU'),
];
// Обробляємо властивості через маппінг
$properties = [];
foreach ($mapping as $akeneoCode => $bitrixCode) {
$value = getAkeneoValue($product['values'][$akeneoCode] ?? [], 'ru_RU');
if ($value !== null) {
if (in_array($bitrixCode, ['DETAIL_TEXT', 'PREVIEW_TEXT', 'NAME'])) {
$fields[$bitrixCode] = $value;
} else {
$properties[$bitrixCode] = $value;
}
}
}
if ($existing) {
$el->Update($existing['ID'], $fields);
CIBlockElement::SetPropertyValuesEx($existing['ID'], CATALOG_IBLOCK_ID, $properties);
} else {
$fields['IBLOCK_SECTION_ID'] = resolveCategoryId($product['categories'][0] ?? null);
$newId = $el->Add($fields);
if ($newId) {
CIBlockElement::SetPropertyValuesEx($newId, CATALOG_IBLOCK_ID, $properties);
}
}
}
function getAkeneoValue(array $values, string $locale): mixed
{
foreach ($values as $entry) {
if (($entry['locale'] === $locale || $entry['locale'] === null)
&& $entry['scope'] === null) {
return $entry['data'];
}
}
return null;
}
Синхронізація медіафайлів
Зображення в Akeneo зберігаються як медіафайли. Атрибут типу pim_catalog_image повертає код файлу (_media_links). Завантажуємо та зберігаємо в Бітріксі:
function syncProductImage(int $productId, string $mediaCode, AkeneoClient $client): void
{
$cacheKey = 'akeneo_media_' . md5($mediaCode);
$cache = \Bitrix\Main\Data\Cache::createInstance();
if ($cache->initCache(86400 * 7, $cacheKey, '/akeneo/media')) {
return; // вже завантажено
}
$imageContent = $client->getMediaFile($mediaCode);
$ext = pathinfo($mediaCode, PATHINFO_EXTENSION) ?: 'jpg';
$tmpFile = sys_get_temp_dir() . '/' . $productId . '_' . uniqid() . '.' . $ext;
file_put_contents($tmpFile, $imageContent);
$fileId = \CFile::MakeFileArray($tmpFile);
\CIBlockElement::SetPropertyValues($productId, CATALOG_IBLOCK_ID, $fileId, 'MORE_PHOTO');
unlink($tmpFile);
$cache->startDataCache(86400 * 7, $cacheKey, '/akeneo/media');
$cache->endDataCache([]);
}
Синхронізація категорій
Ієрархія категорій Akeneo синхронізується в розділи інфоблоку:
GET /api/rest/v1/categories?parent=master
Відповідь містить code, parent, labels. Рекурсивно створюємо розділи інфоблоку через CIBlockSection::Add(), зберігаючи відповідність akeneo_code ↔ IBLOCK_SECTION_ID в HL-блоці AkeneoMapping.
Тривалість реалізації
| Обсяг | Склад | Тривалість |
|---|---|---|
| 1,000–5,000 SKU, базові атрибути | Клієнт + маппінг + агент | 1–2 тижня |
| 10,000–50,000 SKU + медіа + i18n | Інкрементальна синхронізація + очередь обробки | 3–4 тижня |
| Двостороння синхронізація + product models | Повний двонаправлений обмін + Webhook | 5–7 тижнів |







