A category manager spends up to 30% of their time manually checking prices — opening product cards, comparing with competitors, repeating the cycle. With a catalog of 50,000 SKUs, that's hundreds of man-hours per month that could be spent on strategic pricing. We set up a dashboard that shows problematic positions within 30 seconds, the difference in rubles and percentages, and allows price changes right on the screen. This approach reduces analysis time by 10x compared to manual comparison and lowers the chance of errors when copying data.
On one project with a catalog of 120,000 products, we implemented the dashboard in 8 days. After launch, managers reduced time on price analysis from 4 hours to 15 minutes per day. Average time savings on price analysis — 30%, implementation payback is 2–3 months due to reduced manual labor. We'll estimate your project in 1 day and provide a quote without overcharges. Contact us for a consultation.
Data Sources
The dashboard is built on three tables:
-
bl_competitor_prices — current competitor prices
-
bl_product_price_position — aggregates (min/max/average of competitors, our rank)
-
b_catalog_price — our current prices
Data in the aggregate table is updated by an agent after each competitor price synchronization. To speed up queries, we add indexes on product_id, rank, and updated_at fields. SQL optimization allows processing catalogs up to 1 million products without noticeable delays. For catalogs over 500,000 SKUs, we additionally set up table partitioning and tagged caching via Bitrix Cache.
Why a Heat Map by Sections Is Key to Quick Diagnosis
Instead of scrolling through endless products, the manager immediately sees which catalog section is "on fire". We generate a map: green (>70% in 1st place), yellow (50–70%), red (<50%). This cuts analysis time by 10x compared to manual checking.
// Query for section aggregates
$sectionStats = \Bitrix\Main\Application::getConnection()->query("
SELECT
s.NAME as section_name,
COUNT(*) as total_products,
COUNT(CASE WHEN ppp.rank = 1 THEN 1 END) as on_first_place,
ROUND(AVG(ppp.rank), 1) as avg_rank,
COUNT(CASE WHEN ppp.our_price > ppp.min_comp THEN 1 END) as losing_count
FROM bl_product_price_position ppp
JOIN b_iblock_element ie ON ie.ID = ppp.product_id
JOIN b_iblock_section s ON s.ID = ie.IBLOCK_SECTION_ID
GROUP BY s.ID, s.NAME
ORDER BY losing_count DESC
")->fetchAll();
What Metrics Does the Dashboard Show?
Price Position — product distribution by rank:
SELECT rank, COUNT(*) as product_count
FROM bl_product_price_position
WHERE updated_at > NOW() - INTERVAL '24 hours'
GROUP BY rank
ORDER BY rank;
Displayed as a bar chart: "1st place — 34 products, 2nd place — 87, 3rd place — 124..."
Products Where We Are More Expensive Than the Minimum Competitor Price:
SELECT
ie.ID,
ie.NAME,
ppp.our_price,
ppp.min_comp as competitor_min,
ROUND((ppp.our_price - ppp.min_comp) / ppp.min_comp * 100, 1) as diff_pct,
ppp.rank
FROM bl_product_price_position ppp
JOIN b_iblock_element ie ON ie.ID = ppp.product_id
WHERE ppp.our_price > ppp.min_comp
AND ppp.min_comp > 0
ORDER BY diff_pct DESC
LIMIT 50;
Lost Revenue (estimate):
SELECT
SUM(
(ppp.our_price - ppp.min_comp) / ppp.our_price * oe.order_count * ppp.our_price
) as estimated_lost_revenue
FROM bl_product_price_position ppp
JOIN (
SELECT product_id, COUNT(DISTINCT order_id) as order_count
FROM b_sale_basket
WHERE date_insert > NOW() - INTERVAL '30 days'
GROUP BY product_id
) oe ON oe.product_id = ppp.product_id
WHERE ppp.our_price > ppp.min_comp;
How to Update Data Without Reloading?
The "Refresh Data" button triggers an AJAX request to the backend, which synchronizes data with the price source and recalculates aggregates. The interface remains responsive — no F5.
document.getElementById('refresh-btn').addEventListener('click', function() {
this.disabled = true;
fetch('/bitrix/services/main/ajax.php?action=PriceDashboard:refresh', {
method: 'POST',
headers: {'X-Bitrix-Csrf-Token': BX.bitrix_sessid()}
})
.then(r => r.json())
.then(data => {
if (data.status === 'ok') location.reload();
});
});
Dashboard Page Structure
The page at /bitrix/admin/price_dashboard.php consists of three blocks:
Top block — summary KPIs:
- Total products under monitoring: N
- Of which more expensive than competitors: N (XX%)
- Average price position: X.X place
- Products in 1st place: N
Middle block — heat map by catalog sections (described above).
Bottom block — table of problematic products with columns:
| Product |
Art |
Our Price |
Min Competitor |
Difference |
Rank |
[Change Price] |
The "Change Price" button — inline editing with saving via AJAX into b_catalog_price. Upon saving, the bl_price_change_log logs who changed, when, from which price, to which price.
Details of Inline Editing Implementation
For inline editing, we use the `bitrix:main.ui.grid` component with a custom action. After changing the price, a request is sent to `/bitrix/services/main/ajax.php?action=PriceDashboard:updatePrice` which validates data, writes to `b_catalog_price` and the log. If the price goes out of allowable limits, the user receives an error message.
Work Process
- Analysis — we study your current catalog, competitor price sources, typical manager queries. Define key metrics.
- Design — design HL-block structure, agents, SQL queries, and interface.
- Implementation — write code: aggregate queries, dashboard with Chart.js, inline editing, export.
- Testing — test on real data, measure performance, fix bottlenecks.
- Launch — deploy to production, train managers, hand over documentation.
Export to Excel
The "Export to Excel" button generates a report via \PhpOffice\PhpSpreadsheet: all products with competitor prices in separate columns (each competitor is a separate column), our price, position, recommended price (if a repricer is configured).
What's Included in the Work (Deliverables)
- Configured aggregate tables with optimized indexes
- SQL queries for KPIs, heat map, and problem list
- Dashboard interface (PHP + JS + Chart.js)
- Inline price editing with logging
- Export to Excel
- Manager manual on working with the dashboard
- 30-day warranty on stable operation
Timelines
| Stage |
Time |
| Aggregate queries and optimization |
2 days |
| Top KPI block + Chart.js |
1 day |
| Heat map by sections |
1 day |
| Problem product table + inline editing |
2 days |
| Excel export |
1 day |
| Testing |
1 day |
| Total |
8–9 days |
Common Mistakes When Implementing a Dashboard
Ignoring indexes. Without proper indexes (especially on product_id and rank), queries on a catalog of 100,000 products can take minutes. We always check EXPLAIN and add composite indexes.
Synchronization during peak load. Updating aggregates via agent once an hour is usually safe, but if parsing runs during active manager work, it's better to shift the schedule to night or use a job queue.
Lack of change logging. Without the bl_price_change_log, it's impossible to track who changed prices and when. This is critical for reports and audits.
With our experience (more than 50 dashboard implementations on Bitrix), we avoid these pitfalls. Certified specialists with 7+ years of working with 1C-Bitrix guarantee a stable solution. Order a turnkey dashboard setup.
Get a consultation — contact us, we'll evaluate your project and prepare a roadmap. Your commercial manager will get a tool that really saves time. For details on working with HL-blocks, refer to the official 1C-Bitrix documentation for Highload blocks.
What Typical Pricing and Discount Issues Do We Solve?
We often encounter scenarios where a marketer launches a "20% off electronics" campaign, a manager manually sets a special price for a VIP client, and the loyalty system adds another 10%. The result: the customer sees 44% off instead of the planned 20%, and the product goes below cost. The root cause is incorrect cart rule priorities in the sale module and conflicts between price types in b_catalog_price. Proper pricing and discount configuration in 1C-Bitrix eliminates chaos and maintains margins even with hundreds of active promotions. We can assess your project in one day—just get in touch.
How to Configure Price Types and Select Strategy?
Bitrix stores prices in the b_catalog_price table—one row per price type per product. Price types are defined in b_catalog_group and linked to user groups via b_catalog_group2group. Proper price type configuration is the foundation for any discount mechanics.
| Price Type |
Linkage |
How It Works |
| Retail |
Group "All Users" |
Default site price |
| Wholesale |
Group "Wholesale" |
Automatically after wholesale login |
| Dealer |
Group "Dealers" |
Individual coefficient from base |
| Purchase |
For internal accounting only |
Cost price, hidden from users |
| Old Price |
For strikethrough price |
"Was X, now Y" |
| Regional |
Geo-linked |
Prices considering regional logistics |
For each type, we configure:
- Automatic calculation through markup/discount formulas from the base (
CCatalogProductProvider or OnGetOptimalPrice handler)
- Currency and rounding rules in
b_catalog_rounding
- CSV import/export and 1C synchronization (CommerceML)
Multi-currency is implemented via exchange rate updates using \Bitrix\Currency\CurrencyManager::updateCBRFRates() or manually in b_catalog_currency. Displaying prices in the user's currency is done by geolocation (via geoip) or profile settings. Discounts work correctly after conversion: the percentage is calculated from the converted amount.
Cart Rules: How to Avoid Discount Conflicts
The sale module, section "Cart Rules" (/bitrix/admin/sale_discount.php), is a rule builder that requires no development skills but can easily break everything.
Common scenarios:
- Discount based on amount:
BASKET_AMOUNT >= 5000 → DISCOUNT 10%
- "3 for the price of 2" — condition on cart quantity per catalog section
- Bundle discount: "Phone + case + glass = 15% off" — via rule with multiple conditions
PRODUCT_ID IN (...)
- Timer: discount active from 23:00 to 07:00 via
ACTIVE_FROM / ACTIVE_TO fields
- Group discount: check
USER_GROUP in rule conditions
Priorities — Where Mistakes Usually Happen
Two 20% discounts do not equal 40%. With sequential application: 100 → 80 → 64, net 36% off. With parallel: 100 − 20 − 20 = 60, net 40% off. If priorities are not set, Bitrix may apply both as separate rules and give 36% off. Or the opposite.
We configure:
- The
PRIORITY field for application order
- The
LAST_DISCOUNT = Y flag to indicate "do not apply other discounts after this one"
- A maximum percentage through a custom
OnBeforeSaleOrderFinalAction handler
- Exclusion of products/categories from rules via
EXCLUDE conditions
Our priority setup with LAST_DISCOUNT reduces the likelihood of discount conflicts by five times compared to chaotic application. In 8 out of 10 stores where discounts unexpectedly "stacked," the issue was priorities and the absence of the LAST_DISCOUNT flag. We fix this during the audit phase.
How to Avoid Conflicts in Cart Rules?
Without clear priorities, a cascade of uncontrolled discounts is easy to trigger. The solution is to set the application order via PRIORITY and prohibit further discounts with LAST_DISCOUNT = Y. For complex promotions (e.g., cumulative + promo code), we use custom handlers that compare the final discount against the allowable margin. This ensures the customer never leaves with a loss-making checkout.
Cumulative Discounts and Loyalty Programs
Four models to choose from:
-
Threshold-based — discount increases with purchase total. Simpler for customers and support.
- Points-based — points earned from purchases, redeemed for rewards. More flexible but harder to understand.
- Tiered — Silver/Gold/Platinum. Gamification retains customers.
- Cashback — returned to internal account (
b_sale_user_account).
Threshold System: Example Implementation
| Purchase Total Range |
Level |
Discount |
| Up to a certain threshold |
Standard |
0% |
| From moderate amount |
Silver |
5% |
| From higher amount |
Gold |
10% |
| Above highest threshold |
Platinum |
15% |
Technically: the OnSaleOrderPaid handler recalculates the total of paid orders via CSaleOrder::GetList() with the filter PAYED = Y, updates the user group via CUser::SetUserGroup(). The group is linked to a price type—the discount applies automatically on the next visit.
Additional features:
- Notification "You need just $X more to reach Gold status" — via a custom component in the personal account.
- Level validity period — annual (recalculated by
CAgent) or permanent.
- Separate calculation per category — electronics purchases do not affect clothing status.
Formula for Calculating Cumulative Discount
The total of paid orders over a period (default 12 months) is summed, then compared to thresholds. When a new threshold is reached, the user is moved to the corresponding group. Example: a customer has made purchases totaling $X — they are in "Silver" (5%). After the next purchase of $Y, the total reaches a higher threshold, triggering the move to "Gold" (10%).
How It Works in Practice: A Case Study
We recently set up a threshold-based loyalty program for an online home appliance store with a product range of 15,000 SKUs. Previously, there was no loyalty system, and discounts were given manually by managers. We implemented a four-level threshold system. Result: repeat purchases increased by 40% over six months, and margins did not drop—the discount rarely exceeded 10% of the average cart.
Promo Codes and Their Possibilities
Management via CSaleDiscount and a custom administrative interface:
- Single-use — unique code linked to a coupon (
b_sale_discount_coupon).
- Multi-use — shared code with a usage limit via
MAX_USE.
- Personal — linked to
USER_ID.
- Bulk generation —
CSaleDiscountCoupon::Add() in a loop, generating thousands per minute.
Restrictions: minimum order amount, product categories, per-user limit, validity period, compatibility with other discounts. Statistics—who used which code, when, and with what checkout amount—via a report on b_sale_discount_coupon with a JOIN on b_sale_order. Linking to UTM tags shows which channel actually drives conversions.
Wholesale Pricing (B2B)
Mechanisms not available out of the box:
- Automatic price type switch when quantity > N via
OnGetOptimalPrice handler.
- Price scale display on the product card via a custom component: "1–9 pcs: $X, 10–49: $Y, 50–99: $Z, 100+: $W".
- Personal price lists — PDF/Excel generation from the personal account via PhpSpreadsheet.
- Special price request form → lead in CRM.
- Credit limit and deferred payment via
b_sale_user_account and a custom payment handler.
Promotions and Personalization
Scheduling via ACTIVE_FROM / ACTIVE_TO — automatic start and end. Countdown timer — JS component linked to the item's ACTIVE_TO. Limiting promotional item quantity via the QUANTITY_LIMIT property and cart handler checks. A "Promotions" section — via smart filter on the IS_SALE = Y property.
Types: sale, product of the day (rotated by agent), flash sale, clearance, seasonal.
Personalization:
- VIP discounts via individual user group → personal price type.
- Corporate terms: deferred payment, custom delivery.
- Behavioral segmentation via
b_sale_order → automatic discount assignment.
- Dynamic pricing — custom module adjusting price based on demand, stock, and competitor prices.
Integration with 1C
- Import price types via CommerceML (standard exchange
bitrix:catalog.import.1c).
- Sync discount cards: card number → user group → price type.
- Rounding rules and VAT — alignment between 1C and Bitrix to ensure the site price matches the invoice.
- Scheduled updates (cron + agent) or real-time via REST API.
How We Configure Prices and Discounts: Step-by-Step Process
- Audit of the current pricing system — identifying rule conflicts, priority errors, unused price types.
- Development of discount scheme — considering margins and business logic (cumulative, wholesale, promo codes, personalization).
- Cart rule configuration — priorities, flags, exceptions.
- 1C integration — synchronization of price types, discount cards, rounding.
- Testing — load testing with 100+ active rules, conflict checks.
- Documentation — description of all settings, instructions for marketers.
- Manager training — how to create and disable promotions without risk.
- 30-day support — fix any anomalies after launch.
Timelines
| Task |
Timeline |
| Audit and price type setup |
2–3 days |
| Basic cart rules |
3–5 days |
| Cumulative discount system |
1–2 weeks |
| B2B pricing |
2–4 weeks |
| Promo code system |
1 week |
| Comprehensive pricing system |
4–8 weeks |
Cost is calculated individually—it depends on the depth of the audit and the number of products. Our accumulated experience (over 7 years) and certified specialists ensure your margins remain under control. Get a consultation on pricing and discount configuration—contact us, and we will assess your project in one day.