Разработка личного кабинета поставщика при дропшиппинге 1С-Битрикс
При дропшиппинге поставщик должен видеть заказы, которые относятся именно к его товарам, обновлять остатки и цены, подтверждать отгрузку. Стандартный личный кабинет Битрикс (my) заточен под покупателей. Кабинет поставщика — отдельный раздел с доступом по роли, собственными компонентами и ограниченной видимостью данных.
Модель доступа и роли
В Битрикс разграничение прав реализуется через группы пользователей. Создаём группу «Поставщики» через API или административный интерфейс:
$groupId = CGroup::Add([
'ACTIVE' => 'Y',
'NAME' => 'Поставщики',
'STRING_ID' => 'SUPPLIERS',
]);
Привязка конкретного поставщика к его товарам — через HL-блок SupplierProduct или свойство инфоблока. Проще всего — свойство SUPPLIER_ID типа «Привязка к пользователю» (E) у каждого товара. Тогда поставщик видит только товары, у которых SUPPLIER_ID = текущий пользователь.
Раздел кабинета поставщика закрывается через проверку группы:
if (!$USER->IsAuthorized() || !$USER->IsInGroup($supplierGroupId)) {
LocalRedirect('/auth/?backurl=' . urlencode($_SERVER['REQUEST_URI']));
}
Блок «Мои товары»
Список товаров поставщика с текущим остатком и ценой. Запрос через CIBlockElement::GetList с фильтром по SUPPLIER_ID:
$userId = $USER->GetID();
$res = CIBlockElement::GetList(
['NAME' => 'ASC'],
[
'IBLOCK_ID' => CATALOG_IBLOCK_ID,
'ACTIVE' => 'Y',
'PROPERTY_SUPPLIER_ID' => $userId,
],
false,
false,
['ID', 'NAME', 'DETAIL_PAGE_URL', 'PREVIEW_PICTURE', 'PROPERTY_SUPPLIER_ID']
);
Для каждого товара показываем текущий остаток из b_catalog_store_product и цену из b_catalog_price:
$quantityRes = CCatalogStoreProduct::GetList(
[],
['PRODUCT_ID' => $productId]
);
$totalQuantity = 0;
while ($qRow = $quantityRes->GetNext()) {
$totalQuantity += (int)$qRow['AMOUNT'];
}
Форма обновления остатков и цен
Поставщик должен иметь возможность обновить цену и остаток без доступа к административной части. Форма отправляет AJAX-запрос:
// /local/ajax/supplier-update.php
$productId = (int)$_POST['product_id'];
$newPrice = (float)$_POST['price'];
$newQty = (int)$_POST['quantity'];
// Проверяем, что товар принадлежит текущему поставщику
$ownerCheck = CIBlockElement::GetProperty(
CATALOG_IBLOCK_ID,
$productId,
'sort',
'asc',
['CODE' => 'SUPPLIER_ID', 'VALUE' => $USER->GetID()]
);
if (!$ownerCheck->Fetch()) {
echo json_encode(['error' => 'Доступ запрещён']);
die();
}
// Обновляем цену
CCatalogProduct::Update($productId, []); // обновление без изменения полей продукта
$priceRow = CCatalogPrice::GetList(
[], ['PRODUCT_ID' => $productId, 'CATALOG_GROUP_ID' => BASE_PRICE_GROUP_ID]
)->Fetch();
if ($priceRow) {
CCatalogPrice::Update($priceRow['ID'], ['PRICE' => $newPrice, 'CURRENCY' => 'RUB']);
} else {
CCatalogPrice::Add([
'PRODUCT_ID' => $productId,
'CATALOG_GROUP_ID' => BASE_PRICE_GROUP_ID,
'PRICE' => $newPrice,
'CURRENCY' => 'RUB',
]);
}
// Обновляем остаток на складе поставщика
$storeProductRow = CCatalogStoreProduct::GetList(
[], ['PRODUCT_ID' => $productId, 'STORE_ID' => getSupplierStoreId($userId)]
)->Fetch();
if ($storeProductRow) {
CCatalogStoreProduct::Update($storeProductRow['ID'], ['AMOUNT' => $newQty]);
} else {
CCatalogStoreProduct::Add([
'PRODUCT_ID' => $productId,
'STORE_ID' => getSupplierStoreId($userId),
'AMOUNT' => $newQty,
]);
}
echo json_encode(['success' => true]);
Каждый поставщик имеет свой склад (b_catalog_store) — это позволяет отслеживать остатки по каждому поставщику независимо.
Блок «Мои заказы»
Поставщик видит только те заказы, в которых есть его товары. Прямой запрос к b_sale_order не подходит — нужна связь через корзину:
$supplierId = $USER->GetID();
$connection = \Bitrix\Main\Application::getConnection();
$orders = $connection->query("
SELECT DISTINCT
o.ID,
o.DATE_INSERT,
o.PRICE,
o.STATUS_ID,
o.USER_ID,
u.NAME,
u.LAST_NAME,
u.EMAIL
FROM b_sale_order o
JOIN b_sale_basket b ON b.ORDER_ID = o.ID
JOIN b_iblock_element_property ep
ON ep.IBLOCK_ELEMENT_ID = b.PRODUCT_ID
AND ep.IBLOCK_PROPERTY_ID = " . SUPPLIER_PROP_ID . "
AND ep.VALUE_NUM = {$supplierId}
LEFT JOIN b_user u ON u.ID = o.USER_ID
WHERE o.DATE_INSERT >= DATE_SUB(NOW(), INTERVAL 90 DAY)
ORDER BY o.DATE_INSERT DESC
LIMIT 100
");
В детальной странице заказа поставщик видит только позиции корзины, относящиеся к его товарам — не весь заказ:
$basketItems = $connection->query("
SELECT b.ID, b.NAME, b.QUANTITY, b.PRICE, b.PRODUCT_ID
FROM b_sale_basket b
JOIN b_iblock_element_property ep
ON ep.IBLOCK_ELEMENT_ID = b.PRODUCT_ID
AND ep.IBLOCK_PROPERTY_ID = " . SUPPLIER_PROP_ID . "
AND ep.VALUE_NUM = {$supplierId}
WHERE b.ORDER_ID = {$orderId}
");
Подтверждение отгрузки
Поставщик подтверждает отгрузку своей части заказа. Статус фиксируется в HL-блоке SupplierShipment:
-
UF_ORDER_ID— ID заказа -
UF_SUPPLIER_ID— ID поставщика -
UF_STATUS—pending/confirmed/shipped/delivered -
UF_TRACKING— трек-номер -
UF_DATE_SHIPPED— дата отгрузки
Когда все поставщики заказа установили статус shipped, агент автоматически меняет статус заказа Битрикс на «Отправлен».
Массовая загрузка цен и остатков через CSV
Для поставщиков с большим ассортиментом — форма загрузки CSV-файла:
Артикул;Цена;Остаток
SKU-001;1500;25
SKU-002;2800;10
PHP-обработчик читает CSV через SplFileObject, находит товар по CML2_ARTICLE (свойство артикула), проверяет принадлежность поставщику и обновляет данные. Ограничение на размер файла и количество строк (до 5 000) — чтобы исключить таймаут PHP.
Сроки реализации
| Объём | Состав | Срок |
|---|---|---|
| Базовый кабинет (список заказов + обновление остатков) | Компоненты + AJAX-форма + роль | 1–1.5 недели |
| Полноценный кабинет (склад, цены, отгрузка, CSV) | + загрузка файлов + агент статусов | 2–3 недели |
| Мультиязычный кабинет с аналитикой продаж | + отчёты по продажам + i18n | 3–4 недели |







