Налаштування обліку маркованих товарів на 1С-Bitrix
Облік маркованих товарів торкається всього циклу: приймка від постачальника → зберігання на складі → продаж → повернення. Кожен етап повинен відображатися в системі маркування «Честний Знак». Bitrix сам по собі не є обліковою системою для маркування — але для інтернет-магазинів без 1С можна вистроїти базовий облік прямо на Bitrix'і.
Складський облік маркованих одиниць
Стандартний облік залишків у Bitrix'і (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(). При продажі — декрементуєте. Це зберігає сумісність з компонентами каталогу Bitrix'а, які читають залишки зі стандартної таблиці.
Резервування при оформленні замовлення
При додаванні в кошик або при оформленні замовлення маркований товар потрібно зарезервувати — переводити код у статус 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;
Щоденна звірка: кількість проданих кодів повинна збігатися з кількістю товарів у виконаних замовленнях. Розбіжність сигналізує про помилку в обробниках подій.







