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.







