Настройка учёта маркированных товаров на 1С-Битрикс
Учёт маркированных товаров затрагивает весь цикл: приёмка от поставщика → хранение на складе → продажа → возврат. Каждый этап должен отражаться в системе маркировки «Честный Знак». Битрикс сам по себе не является учётной системой для маркировки — но для интернет-магазинов без 1С можно выстроить базовый учёт прямо на Битриксе.
Складской учёт маркированных единиц
Стандартный учёт остатков в Битриксе (b_catalog_store_product) работает с количеством, а не с конкретными экземплярами. Для маркированных товаров нужен учёт на уровне серийных номеров.
Создаёте таблицу серийных номеров (кодов маркировки) и привязываете к складу:
CREATE TABLE b_local_marking_inventory (
ID INT AUTO_INCREMENT PRIMARY KEY,
PRODUCT_ID INT NOT NULL, -- ID товара из b_iblock_element
STORE_ID INT, -- ID склада из b_catalog_store
CODE VARCHAR(200) NOT NULL, -- Data Matrix код
GTIN CHAR(14),
SERIAL VARCHAR(20),
STATUS ENUM('received','reserved','sold','returned','defective') DEFAULT 'received',
ORDER_ID INT, -- при статусе sold/reserved
RECEIVED_AT DATETIME,
UPDATED_AT DATETIME ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_product_status (PRODUCT_ID, STATUS),
INDEX idx_code (CODE)
);
Связь с таблицей b_catalog_store_product: при добавлении записи в b_local_marking_inventory со статусом received инкрементируете остаток через CCatalogStoreProduct::Update(). При продаже — декрементируете. Это сохраняет совместимость с компонентами каталога Битрикса, которые читают остатки из стандартной таблицы.
Резервирование при оформлении заказа
При добавлении в корзину или при оформлении заказа маркированный товар нужно резервировать — переводить код в статус reserved с привязкой к ORDER_ID. Это предотвращает продажу одного экземпляра двум покупателям.
Обработчик на событие OnSaleBasketItemAdd:
AddEventHandler("sale", "OnSaleBasketItemAdd", function(&$arFields) {
$productId = $arFields['PRODUCT_ID'];
if (isMarkedProduct($productId)) {
// Находим свободный код для товара
$code = \Local\MarkingCode\InventoryTable::getList([
'filter' => ['PRODUCT_ID' => $productId, 'STATUS' => 'received'],
'limit' => 1,
'select' => ['ID', 'CODE'],
])->fetch();
if (!$code) {
// Нет доступных экземпляров — блокируем добавление
return false;
}
// Резервируем
\Local\MarkingCode\InventoryTable::update($code['ID'], ['STATUS' => 'reserved']);
// Сохраняем ID кода в свойство позиции корзины
$arFields['PROPS'][] = ['NAME' => 'MARKING_CODE_ID', 'VALUE' => $code['ID']];
}
});
При отмене заказа — освобождаете зарезервированные коды обратно в статус received. Обработчик на OnSaleOrderStatusUpdate при переходе в статус отмены.
Списание при успешной продаже
При получении оплаты (событие OnSaleOrderPaid или OnSalePaymentPaid) все зарезервированные коды переводите в sold и ставите в очередь на отправку уведомления в ГИС МТ:
AddEventHandler("sale", "OnSaleOrderPaid", function($id, $arOrder) {
$markingCodes = \Local\MarkingCode\InventoryTable::getList([
'filter' => ['ORDER_ID' => $id, 'STATUS' => 'reserved'],
]);
while ($code = $markingCodes->fetch()) {
\Local\MarkingCode\InventoryTable::update($code['ID'], ['STATUS' => 'sold']);
\Local\MarkingCode\NotificationQueue::add([
'CODE' => $code['CODE'],
'ORDER_ID' => $id,
'OPERATION' => 'SALE',
]);
}
});
Отчётность по маркированным товарам
Для аналитики — административная страница в /local/admin/marking_report.php с выборкой по статусам, датам, товарам. Агрегация через прямые SQL-запросы к b_local_marking_inventory:
SELECT PRODUCT_ID,
COUNT(*) as total,
SUM(STATUS = 'received') as in_stock,
SUM(STATUS = 'sold') as sold
FROM b_local_marking_inventory
GROUP BY PRODUCT_ID;
Ежедневная сверка: количество проданных кодов должно совпадать с количеством товаров в выполненных заказах. Расхождение сигнализирует об ошибке в обработчиках событий.







