The Problem: Fixed Prices for Each Dealer
A dealer receives a catalog with prices that don't match the published ones. Group discounts don't provide the needed flexibility: each wholesaler requires a personal price as per contract. The standard CCatalogDiscount mechanism is tied to price types and percentages—you can't fix an absolute value that remains unchanged when the base price is recalculated. Contract prices solve this: a fixed record in a separate storage overrides any discounts, but only for a specific counterparty for the contract duration.
Our implementation uses Highload-blocks and a custom price provider, processing up to 100,000 contract records in 50ms per order. We rely on our own experience: over 7 years working with Bitrix, 50+ projects for dealer networks. We guarantee contract price priority over any promotions. Redis caching with invalidation on contract change ensures minimal response time for 500 concurrent users. First demonstration of operation—3 days after project start.
Why Standard Discounts Don't Work
The catalog module provides discounts (CCatalogDiscount) and price types (b_catalog_group). Discounts are set as a percentage or amount, tied to a price type. For contract prices this is unsuitable: if the base price changes, the contract price must remain at the previous level. Additionally, discounts apply to the whole group—you cannot set a personal price for one dealer without affecting others. More about price types can be found in the 1C-Bitrix documentation.
How We Store Contract Prices
We create a Highload-block dealer_contract_prices. The table is generated automatically based on the block name. Fields:
| Field | Type | Description |
|---|---|---|
UF_DEALER_ID |
Integer | Dealer company ID |
UF_PRODUCT_ID |
Integer | Product ID |
UF_XML_ID |
String | Article (for 1C sync) |
UF_PRICE |
Double | Contract price |
UF_CURRENCY |
String | Currency |
UF_DATE_FROM |
DateTime | Start of validity |
UF_DATE_TO |
DateTime | End of validity |
UF_ACTIVE |
Boolean | Activity status |
An index on (UF_DEALER_ID, UF_PRODUCT_ID, UF_ACTIVE) is mandatory: without it, queries on 100,000+ records degrade by tens of times. We use a composite index to cover the main scenario: WHERE dealer=... AND product=... AND active=1.
How the Application Logic Works
We implement a custom price provider hooked to the OnSaleBasketBeforeSaved event or via Bitrix\Catalog\v2\Price extension. Algorithm:
- Determine the current user's company from the session.
- Search in
dealer_contract_pricesfor a record matching dealer, product, dates, and activity. - If found, use
UF_PRICEas the final price, ignoring the standard price type. - If not found, fall back to the dealer's price type based on their group.
The result is cached in Redis with key contract_price_{dealerId}_{productId} for 1 hour. Invalidation occurs on record update in the Highload-block via OnAfterHLBlockElementUpdate. This approach is 5 times faster than iterating all catalog module discounts with 50,000 products.
How to Load Prices from 1C
Contract prices are managed in 1C. Synchronization via an agent:
- 1C exports XML with dealer (1C code), article, price, and period.
- Agent finds the dealer by
UF_1C_IDin the company Highload-block, product byXML_ID. - Creates or updates the record in
dealer_contract_prices. - Marks expired records with
UF_ACTIVE= false.
Frequency: event-driven on price list updates, or via cron twice daily. Synchronization errors are logged: if a contract price is missing, the dealer sees the base price with a warning.
Why Redis Over File Cache for Contract Prices?
The file cache in Bitrix (BXCache) works at the page and component level. For contract prices, we need a cache that quickly serves single records. Redis in memory provides response time <1 ms. Important to configure tagged caching: invalidation by tag contract:dealer:{id} on contract change. This avoids full cache flush and saves server resources.
How We Test Load with Contract Prices
Before launch, we conduct load testing. Conditions: 500 concurrent users, 100,000 contract records, basket query with 20-30 items. Expected response time—no more than 100 ms. We use ab or locust. If issues arise, we add middleware to smooth peaks. Results are documented in a report.
What Is Included in Contract Price Setup
| Component | Description |
|---|---|
| Highload-block | Fields for your nomenclature, indexes, cache settings |
| Price provider | Custom with Redis support, OnSaleBasketBeforeSaved event |
| Sync agent | Read XML from 1C, update records, log errors |
| Catalog display | "Contract" label in result_modifier.php |
| Testing | Load test: 500 simultaneous orders, response <100 ms |
| Training | Documentation, tutorial on updating directories |
Timeline and Team Experience
Storage and logic setup—1-2 weeks. With 1C export integration—2-3 weeks. We provide a free project assessment with an estimate broken down into stages. Our team includes certified 1C-Bitrix specialists with over 5 years of experience.
Get a consultation for your project—we'll calculate the timeline and cost for your catalog. Contact us to discuss the details.

