Development of size selection based on parameters 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
    1175
  • 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
    747
  • 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

Developing Size Selection by Measurements for 1C-Bitrix

A size converter helps those who already know their size in one system. Size selection by measurements solves a different problem: the buyer knows their measurements — height, weight, chest circumference, foot length — but does not know which size will fit them in a specific brand. Or they know that with different manufacturers they wear different sizes, and want a recommendation specifically for the item in question.

This is the "smartest" of all size navigation tools — and the most complex to implement.

The Challenge

Size selection by measurements requires linking three sets of data:

  1. Buyer's measurements — height, weight, chest/waist/hip circumference, foot length
  2. Product size grid — the correspondence between size labels and physical measurements (accounting for fit allowances: slim or relaxed cut)
  3. Stock levels — the recommended size may be out of stock; an alternative must be suggested

And while for clothing the selection is relatively straightforward (chest → size), for footwear the question of width enters the picture (a wide foot requires a different last), and for sporting goods — technical specifications (stick length based on height and playing style).

Data Architecture

We extend the size chart structure with additional fit tolerance parameters:

CREATE TABLE b_size_fit_rules (
    ID           SERIAL PRIMARY KEY,
    CHART_ID     INT NOT NULL REFERENCES b_size_charts(ID),
    SIZE_RU      VARCHAR(10),
    -- Measurement ranges for this size
    CHEST_MIN    NUMERIC(5,1), CHEST_MAX    NUMERIC(5,1),
    WAIST_MIN    NUMERIC(5,1), WAIST_MAX    NUMERIC(5,1),
    HIPS_MIN     NUMERIC(5,1), HIPS_MAX     NUMERIC(5,1),
    HEIGHT_MIN   SMALLINT,     HEIGHT_MAX   SMALLINT,
    WEIGHT_MIN   SMALLINT,     WEIGHT_MAX   SMALLINT,
    -- Fit type (affects tolerance)
    FIT_TYPE     VARCHAR(20)  -- 'slim', 'regular', 'oversized'
);

The key point is measurement ranges, not exact values. Size 46 fits a chest of 88–92 cm, not strictly 90 cm. The width of the range depends on the fit type: slim fit — narrow range, oversized — wide range.

Selection Algorithm

// SizeFitAdvisor.php
class SizeFitAdvisor {
    public function recommend(array $measurements, int $chartId, string $fitType = 'regular'): array {
        $rules = SizeFitRulesTable::getList([
            'filter' => ['=CHART_ID' => $chartId, '=FIT_TYPE' => $fitType],
            'order'  => ['SIZE_RU' => 'ASC'],
        ])->fetchAll();

        $scores = [];
        foreach ($rules as $rule) {
            $score = 0;
            $matched = 0;

            // For each provided measurement, check whether it falls within the range
            foreach (['CHEST', 'WAIST', 'HIPS'] as $param) {
                if (!isset($measurements[$param])) continue;
                $val = (float) $measurements[$param];
                $min = (float) $rule[$param . '_MIN'];
                $max = (float) $rule[$param . '_MAX'];
                if ($val >= $min && $val <= $max) {
                    $score++;
                } elseif ($val < $min) {
                    $score -= ($min - $val) / 10; // penalty for deviation
                } else {
                    $score -= ($val - $max) / 10;
                }
                $matched++;
            }

            if ($matched > 0) {
                $scores[$rule['SIZE_RU']] = $score / $matched;
            }
        }

        arsort($scores); // sort by descending score
        $best = array_key_first($scores);
        $next = array_keys($scores)[1] ?? null; // second option in case of unavailability

        return ['primary' => $best, 'alternative' => $next, 'scores' => $scores];
    }
}

The algorithm returns not one size, but a primary recommendation and an alternative. This is important: if the primary size is unavailable, we show the alternative with a note such as "if 46 is out of stock, try 48 — it will work for your build."

Checking Recommended Size Availability

After obtaining the recommendation — immediately check stock levels:

public function checkAvailability(string $sizeRu, int $productId): array {
    // Find trade offers with this size
    $offers = \Bitrix\Iblock\ElementTable::getList([
        'filter' => [
            '=IBLOCK_ID'            => OFFERS_IBLOCK_ID,
            '=PROPERTY_CML2_LINK'   => $productId,
            '=PROPERTY_SIZE_VALUE'  => $sizeRu,
            '=ACTIVE'               => 'Y',
        ],
        'select' => ['ID', 'PROPERTY_QUANTITY'],
    ])->fetchAll();

    $available = array_filter($offers, fn($o) => (int)$o['PROPERTY_QUANTITY_VALUE'] > 0);

    return [
        'available' => count($available) > 0,
        'offers'    => $available,
    ];
}

Selection Form on the Product Card

UX pattern: a "Find my size" link below the size selector opens a modal window or expands a form.

Single-step form (all measurements at once) — for experienced shoppers:

<form class="size-advisor-form">
    <div class="form-row">
        <label>Height (cm): <input type="number" name="height" min="140" max="220"></label>
        <label>Weight (kg): <input type="number" name="weight" min="40" max="200"></label>
    </div>
    <div class="form-row">
        <label>Chest circumference (cm): <input type="number" name="chest" min="60" max="160"></label>
        <label>Waist circumference (cm): <input type="number" name="waist" min="50" max="150"></label>
        <label>Hip circumference (cm): <input type="number" name="hips" min="70" max="170"></label>
    </div>
    <label>Fit:
        <select name="fit_type">
            <option value="slim">Slim</option>
            <option value="regular" selected>Regular</option>
            <option value="oversized">Oversized</option>
        </select>
    </label>
    <button type="submit">Find my size</button>
</form>

Step-by-step questionnaire — for users unfamiliar with such forms. One field per screen, with a progress bar. Less cognitive load, but a longer process.

AJAX Request to the PHP Controller

The form sends data to the server rather than being processed in JS, because:

  • The selection algorithm is server-side — data is not exposed to competitors
  • The server checks stock immediately and returns a final response
  • The result can be personalised (measurements saved for an authenticated user)
// SizeAdvisorController.php — AJAX action
public function recommendAction(): array {
    $measurements = [
        'CHEST'  => (float) $this->request->getPost('chest'),
        'WAIST'  => (float) $this->request->getPost('waist'),
        'HIPS'   => (float) $this->request->getPost('hips'),
    ];
    $fitType   = $this->request->getPost('fit_type', 'regular');
    $productId = (int) $this->request->getPost('product_id');
    $chartId   = $this->getChartForProduct($productId);

    $advisor    = new SizeFitAdvisor();
    $result     = $advisor->recommend($measurements, $chartId, $fitType);
    $availability = $advisor->checkAvailability($result['primary'], $productId);

    // Save measurements for the authenticated user
    if (is_object(global_user()) && !global_user()->IsGuest()) {
        UserMeasurementsTable::saveForUser(global_user()->GetID(), $measurements);
    }

    return [
        'recommended_size' => $result['primary'],
        'alternative_size' => $result['alternative'],
        'available'        => $availability['available'],
        'offers'           => $availability['offers'],
    ];
}

Saving Measurements in the User Account

If the user is authenticated, measurements are saved and pre-filled the next time the advisor is used — even on a different product:

CREATE TABLE b_user_measurements (
    USER_ID    INT PRIMARY KEY REFERENCES b_user(ID),
    HEIGHT_CM  SMALLINT,
    WEIGHT_KG  SMALLINT,
    CHEST_CM   NUMERIC(5,1),
    WAIST_CM   NUMERIC(5,1),
    HIPS_CM    NUMERIC(5,1),
    FOOT_MM    INT,
    DATE_UPDATE TIMESTAMP DEFAULT NOW()
);

In the user account — a "My Measurements" section for manual editing.

Timelines

Option What's included Timeline
Basic advisor (JS + data table) Form, algorithm, result 1–2 weeks
With stock check and alternatives + TO integration, stock levels 2–3 weeks
+ Account with measurements, recommendation history + profile saving +1 week

A measurement-based size advisor provides the most accurate recommendation and maximally reduces the probability of returns. Of all the size tools, it is the most labour-intensive — but also the most effective.