Setting up mass price updates 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
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1173
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    745
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

Setting Up Bulk Price Updates in 1C-Bitrix

A supplier sends a price list with new purchase prices — 3,000 items. Retail markup is 40%. You need to update retail prices before morning. Manual updates are impossible. Integration via 1C isn't always available. Direct API updates through Bitrix — a solution achievable in several hours of configuration.

Price Table and Price Types

Prices are stored in b_catalog_price. Key fields: PRODUCT_ID, CATALOG_GROUP_ID (price type), PRICE, CURRENCY, QUANTITY_FROM, QUANTITY_TO (for price tiers).

Price types are in b_catalog_group: BASE is the base price, others are additional (retail, wholesale, purchase). CATALOG_GROUP_ID = 1 is typically the base price, but this depends on the specific installation.

Get price type ID by name:

$priceType = \Bitrix\Catalog\GroupTable::getList([
    'filter' => ['NAME' => 'Retail'],
    'select' => ['ID'],
])->fetch();
$priceTypeId = $priceType['ID'];

Bulk Updates via D7

Updating a single product's price:

// Check if price exists
$existing = \Bitrix\Catalog\PriceTable::getList([
    'filter' => ['PRODUCT_ID' => $productId, 'CATALOG_GROUP_ID' => $priceTypeId],
    'select' => ['ID'],
])->fetch();

if ($existing) {
    \Bitrix\Catalog\PriceTable::update($existing['ID'], [
        'PRICE'    => $newPrice,
        'CURRENCY' => 'BYN',
    ]);
} else {
    \Bitrix\Catalog\PriceTable::add([
        'PRODUCT_ID'       => $productId,
        'CATALOG_GROUP_ID' => $priceTypeId,
        'PRICE'            => $newPrice,
        'CURRENCY'         => 'BYN',
    ]);
}

For bulk updates — use loops with batching. With 3,000 items, recommended batch size is 100 with 50ms pauses to avoid MySQL load spikes.

Calculating Price by Markup

When loading purchase prices, retail prices calculate automatically:

function calcRetailPrice(float $purchasePrice, float $markup): float {
    return round($purchasePrice * (1 + $markup / 100), 2);
}

foreach ($newPrices as $item) {
    $retail = calcRetailPrice($item['purchase_price'], 40.0);

    updatePrice($item['product_id'], RETAIL_PRICE_TYPE_ID, $retail);
    updatePrice($item['product_id'], PURCHASE_PRICE_TYPE_ID, $item['purchase_price']);
}

Loading from Excel/CSV

Supplier price lists typically come in Excel or CSV. Use PhpSpreadsheet for parsing:

$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load('/path/to/price.xlsx');
$sheet = $spreadsheet->getActiveSheet();

foreach ($sheet->getRowIterator(2) as $row) { // Starting from row 2 (skip header)
    $cells = $row->getCellIterator();
    $cells->setIterateOnlyExistingCells(false);

    $rowData = [];
    foreach ($cells as $cell) {
        $rowData[] = $cell->getValue();
    }

    $articul     = $rowData[0]; // A: SKU
    $newPurchase = (float)$rowData[3]; // D: purchase price

    // Find product by SKU
    $productId = findProductByArticul($articul);
    if ($productId) {
        updatePrice($productId, PURCHASE_PRICE_TYPE_ID, $newPurchase);
        updatePrice($productId, RETAIL_PRICE_TYPE_ID, calcRetailPrice($newPurchase, 40.0));
    }
}

Cache Invalidation After Updates

After bulk price updates, catalog pages show old prices from cache. Clear cache for the entire catalog:

// Clear cache for catalog components
\Bitrix\Main\Application::getInstance()->getTaggedCache()->clearByTag('catalog');

// Or clear by iblock
\Bitrix\Main\Application::getInstance()->getTaggedCache()->clearByTag('iblock_id_' . $iblockId);

Alternative — don't clear cache in bulk, wait for natural expiration (TTL). But if prices changed for a time-limited promotion, waiting isn't acceptable — clearing is mandatory.

Price Change Logging

For audit purposes, save old prices to a custom catalog_price_history table before updating with fields (product_id, price_type_id, old_price, new_price, changed_by, changed_at). This allows rolling back erroneous updates and analyzing price dynamics.