Auto-Filling Stock Levels and Prices from Supplier Price Lists in 1C-Bitrix
A supplier price list — an Excel or CSV file sent on a schedule via email or uploaded to FTP — differs from an API in that it is not updated in real time. For most B2B stores, however, updates once or twice a day are perfectly adequate. The task: extract prices and stock levels from a non-standard file and update b_catalog_price and b_catalog_store_product.
Retrieving the Price List
Email attachment — the most common scenario. Connect to the mailbox via IMAP, search for messages from the supplier, download the attachment:
$imap = imap_open('{mail.example.com:993/imap/ssl}INBOX', $user, $pass);
$emails = imap_search($imap, 'FROM "[email protected]" UNSEEN');
foreach ($emails as $num) {
$structure = imap_fetchstructure($imap, $num);
// extract attachment
}
FTP/SFTP — periodically download the file and compare its modification date against the previous run. If unchanged, skip it.
HTTP URL — the supplier publishes the price list at a URL, sometimes with Basic Auth.
Reading Excel/CSV
Use PhpSpreadsheet (the successor to PHPExcel) for Excel files:
$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load($filePath);
$sheet = $spreadsheet->getActiveSheet();
$rows = $sheet->toArray();
The challenge: every supplier uses a different format. The SKU, price, and stock columns are in different positions, and the header rows vary in depth.
Solution: a configurable column mapping stored in the database:
supplier_id: 42
sku_column: B # SKU in column B
price_column: D # price in column D
qty_column: F # stock in column F
header_rows: 3 # first 3 rows are headers
Updating Prices
Prices in 1C-Bitrix are stored in b_catalog_price. Each price type is a separate record:
$priceType = CCatalogPriceType::GetList([], ['NAME' => 'Purchase'])->Fetch();
CCatalogProduct::SetPrice($elementId, $priceType['ID'], $price, 'RUB');
If the supplier has multiple price types (retail, wholesale, special) — create the corresponding types in 1C-Bitrix and update each separately.
Markup on import: often the purchase price is imported and the retail price is calculated automatically. The markup coefficient is stored in the supplier settings and applied at write time:
$retailPrice = $supplierPrice * $supplier->getMarkupCoefficient();
Updating Stock Levels
b_catalog_store_product is the per-warehouse stock table. If warehouses are not in use, update QUANTITY directly via CCatalogProduct::Update().
With warehouses enabled:
CCatalogStoreProduct::Update($storeProductId, ['AMOUNT' => $qty]);
// or add a new record if it doesn't exist
CCatalogStoreProduct::Add([
'PRODUCT_ID' => $elementId,
'STORE_ID' => $storeId,
'AMOUNT' => $qty,
]);
Product Identification by SKU
The price list contains supplier SKUs; 1C-Bitrix stores its own codes. A lookup table is required. Storage options:
- A
SUPPLIER_SKUproperty on the info block — simple - A
SupplierMappingHighload block with(supplier_id, supplier_sku, element_id)tuples — scalable
For 50,000+ SKUs, build a reverse index in memory when the worker starts: load all pairs into a PHP array once and use it for mapping without repeated DB queries.
Project Timeline
| Phase | Duration |
|---|---|
| File retrieval setup (email/FTP/HTTP) | 4–8 hours |
| Excel/CSV reading with configurable mapping | 1–2 days |
| Price and stock updates in 1C-Bitrix | 1 day |
| Product identification logic, SKU lookup table | 4–8 hours |
| Error handling, logging, scheduling | 4–6 hours |
Total: 4–6 working days per supplier. With multiple suppliers, the configurable mapping pays for itself from the third connection onward.







