Setting up product labeling in 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages

Setting Up Product Marking on 1C-Bitrix

From 2020–2023, most product categories fall under mandatory "Honest Sign" marking: clothing, footwear, medicines, dairy products, tobacco, tires, perfumes, cameras. When selling through an online store, marking codes must be transmitted to the cash receipt, and when receiving and shipping — to the MDLP monitoring system or GIS MT.

Marking Architecture in Bitrix

Bitrix does not have a built-in module for working with "Honest Sign". Integration is built from several components:

  1. Storing marking codes — binding DataMatrix codes to specific product units
  2. Transmitting code to cash receipt — via cash register module (ATOL, YooKassa, etc.)
  3. Reporting to GIS MT — when sold, the code is removed from circulation

Marking code (КМ) is a string like 010460437166456221hHdB3ePhGH1I\u001d91EE06\u001d92XqnSm5YoGVLWJjOz...

Storing Marking Codes

Codes are bound to SKU (trade offers) of the catalog information block. Storage options:

Option 1 — Information Block Property. Add property MARKING_CODE type "String" or "HTML/Text" to the information block of trade offers:

// Create property programmatically
CIBlockProperty::Add([
    'NAME' => 'Marking Code',
    'CODE' => 'MARKING_CODE',
    'IBLOCK_ID' => OFFERS_IBLOCK_ID,
    'PROPERTY_TYPE' => 'S',
    'MULTIPLE' => 'N',
    'ACTIVE' => 'Y'
]);

Option 2 — Separate Table for projects with high turnover (10,000+ codes):

CREATE TABLE b_marking_codes (
    ID INT AUTO_INCREMENT PRIMARY KEY,
    PRODUCT_ID INT NOT NULL,
    CODE VARCHAR(255) NOT NULL UNIQUE,
    STATUS ENUM('available', 'reserved', 'sold', 'returned') DEFAULT 'available',
    ORDER_ID INT NULL,
    DATE_SOLD DATETIME NULL,
    INDEX idx_product_status (PRODUCT_ID, STATUS),
    INDEX idx_code (CODE)
);

Binding Marking Code to Basket Item

When adding a product to the basket, a specific code must be reserved:

// Handler OnSaleBasketItemEntitySaved or custom logic
AddEventHandler('sale', 'OnSaleBasketBeforeSaved', function(\Bitrix\Main\Event $event) {
    $basketItem = $event->getParameter('ENTITY');
    $productId = $basketItem->getProductId();

    // Find free marking code
    $res = \Bitrix\Main\Application::getConnection()->query(
        "SELECT ID, CODE FROM b_marking_codes
         WHERE PRODUCT_ID = {$productId} AND STATUS = 'available'
         LIMIT 1 FOR UPDATE"
    );
    $codeRow = $res->fetch();

    if ($codeRow) {
        // Reserve code
        \Bitrix\Main\Application::getConnection()->query(
            "UPDATE b_marking_codes SET STATUS = 'reserved', ORDER_ID = NULL
             WHERE ID = {$codeRow['ID']}"
        );

        // Save code in basket item property
        $basketItem->setField('PROPS', [
            'MARKING_CODE' => $codeRow['CODE']
        ]);
    }
});

Transmitting Code to Cash Receipt

A cash receipt under Federal Law 54-FZ with marked goods must contain the marking code in tag 1163. ATOL Online and other operators accept it in the marking_code field.

Modification of cash register module handler (class extension):

// Intercept receipt position formation
AddEventHandler('sale', 'OnCashboxBuildCheck', function($checkData) {
    foreach ($checkData['ITEMS'] as &$item) {
        $basketItemId = $item['BASKET_ID'] ?? null;
        if ($basketItemId) {
            // Get marking code from item properties
            $res = \Bitrix\Sale\Internals\BasketPropertiesTable::getList([
                'filter' => ['BASKET_ID' => $basketItemId, 'CODE' => 'MARKING_CODE'],
                'select' => ['VALUE']
            ]);
            if ($prop = $res->fetch()) {
                $item['MARKING_CODE'] = $prop['VALUE'];
            }
        }
    }
    return $checkData;
});

Code Removal After Sale

After receipt fiscalization, the marking code must transition to "Sold" status and be removed from circulation in GIS MT. This is either automatically via cash register (if OFD is integrated with "Honest Sign"), or manually via GIS MT API.

Updating code status after payment:

AddEventHandler('sale', 'OnSalePaymentEntitySaved', function(\Bitrix\Main\Event $event) {
    $payment = $event->getParameter('ENTITY');
    if ($payment->getField('PAID') === 'Y') {
        $order = $payment->getOrder();
        $basket = $order->getBasket();
        foreach ($basket as $basketItem) {
            $props = $basketItem->getPropertyCollection();
            // Find marking code in item properties
            foreach ($props as $prop) {
                if ($prop->getField('CODE') === 'MARKING_CODE') {
                    $code = $prop->getField('VALUE');
                    \Bitrix\Main\Application::getConnection()->query(
                        "UPDATE b_marking_codes SET STATUS = 'sold',
                         ORDER_ID = {$order->getId()},
                         DATE_SOLD = NOW()
                         WHERE CODE = '" . \Bitrix\Main\Application::getConnection()->getSqlHelper()->forSql($code) . "'"
                    );
                }
            }
        }
    }
});

Importing Marking Codes

Codes are received from the supplier as a file (CSV, Excel) or via EDI. For bulk import:

// Import script
$codes = file('/path/to/marking_codes.txt', FILE_IGNORE_NEW_LINES);
$db = \Bitrix\Main\Application::getConnection();
$productId = 1234; // SKU ID

foreach (array_chunk($codes, 1000) as $batch) {
    $values = implode(',', array_map(function($code) use ($productId, $db) {
        return "({$productId}, '" . $db->getSqlHelper()->forSql(trim($code)) . "', 'available')";
    }, $batch));

    $db->query("INSERT IGNORE INTO b_marking_codes (PRODUCT_ID, CODE, STATUS) VALUES {$values}");
}