Cost Calculation Form Development on 1C-Bitrix
A cost calculation form is a calculator that allows users to independently estimate the price of a service or product before calling a manager. Used in construction, IT services, manufacturing, logistics, and insurance. The conversion effect is twofold: some users leave after seeing the price — but those who remain come prepared with realistic expectations.
Calculator Types
Simple (deterministic): each parameter has a fixed price. Total = sum of components. Example: selecting a car configuration.
Coefficient-based: there is a base price and coefficients — regional, seasonal, by complexity. Total = base_price × K1 × K2 × ... Used in construction and insurance.
Formula-based: an arbitrary formula with multiple input variables. Example: shipping cost = weight × distance × rate + fixed cost.
With range: the result is not an exact amount, but a range "from X to Y". Used in IT development where an exact estimate without a technical specification is impossible.
Storing the Calculator Configuration
It is better to store the configuration in the database (HL-block) rather than in code — so that managers can change prices without a developer.
class CalculatorConfigTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getTableName(): string { return 'b_hl_calculator_config'; }
public static function getMap(): array
{
return [
new IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
new StringField('SLUG'), // Calculator identifier
new StringField('TITLE'),
new TextField('PARAMS_JSON'), // Parameters and their prices
new TextField('FORMULA_JSON'), // Calculation formula
new StringField('CURRENCY'), // RUB, USD, EUR
new FloatField('MIN_PRICE'), // Minimum price
new FloatField('MAX_PRICE'), // NULL = no limit
new BooleanField('SHOW_RANGE', ['values' => [false, true]]),
new IntegerField('RANGE_PERCENT'), // ±% for the range
];
}
}
PARAMS_JSON — parameter descriptions:
[
{
"id": "area",
"label": "Area (sq.m.)",
"type": "range",
"min": 10,
"max": 500,
"default": 50,
"step": 5,
"unit": "sq.m."
},
{
"id": "floors",
"label": "Number of floors",
"type": "select",
"options": [
{"value": 1, "label": "1 floor", "price": 0},
{"value": 2, "label": "2 floors", "price": 15000},
{"value": 3, "label": "3 floors", "price": 35000}
]
},
{
"id": "foundation",
"label": "Foundation type",
"type": "radio",
"options": [
{"value": "tape", "label": "Strip", "price_per_m2": 2500},
{"value": "pile", "label": "Pile", "price_per_m2": 1800},
{"value": "slab", "label": "Slab", "price_per_m2": 4200}
]
},
{
"id": "finishing",
"label": "Finishing",
"type": "checkbox-group",
"options": [
{"value": "rough", "label": "Rough", "price_per_m2": 3000},
{"value": "prefinish", "label": "Pre-finish", "price_per_m2": 5500},
{"value": "finish", "label": "Full finish", "price_per_m2": 9000}
]
}
]
JavaScript Calculation Engine
class PriceCalculator {
constructor(config) {
this.params = config.params;
this.formula = config.formula; // 'params_sum' | 'formula' | string
this.currency = config.currency;
this.minPrice = config.min_price;
this.showRange = config.show_range;
this.rangePercent = config.range_percent || 15;
this.values = {}; // Current parameter values
this.initDefaults();
}
initDefaults() {
this.params.forEach(param => {
if (param.default !== undefined) {
this.values[param.id] = param.default;
} else if (param.type === 'select' || param.type === 'radio') {
this.values[param.id] = param.options[0]?.value;
} else if (param.type === 'checkbox-group') {
this.values[param.id] = [];
}
});
}
setValue(paramId, value) {
this.values[paramId] = value;
}
calculate() {
let total = 0;
const area = parseFloat(this.values['area']) || 1;
this.params.forEach(param => {
const val = this.values[param.id];
if (!val && val !== 0) return;
switch (param.type) {
case 'range':
case 'number':
// Base price per unit (if set)
if (param.price_per_unit) {
total += parseFloat(val) * param.price_per_unit;
}
break;
case 'select':
case 'radio': {
const opt = param.options.find(o => String(o.value) === String(val));
if (opt) {
total += (opt.price || 0) + (opt.price_per_m2 || 0) * area;
}
break;
}
case 'checkbox-group': {
const selected = Array.isArray(val) ? val : [val];
selected.forEach(v => {
const opt = param.options.find(o => String(o.value) === String(v));
if (opt) {
total += (opt.price || 0) + (opt.price_per_m2 || 0) * area;
}
});
break;
}
}
});
if (this.minPrice && total < this.minPrice) {
total = this.minPrice;
}
if (this.showRange) {
const delta = total * (this.rangePercent / 100);
return {
min: Math.floor(total - delta),
max: Math.ceil(total + delta),
exact: null,
};
}
return {min: null, max: null, exact: total};
}
formatPrice(value) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: this.currency,
maximumFractionDigits: 0,
}).format(value);
}
}
Integration with the Application Form
After the calculation — a "Get a quote" button opens a form with the calculation result pre-filled:
document.getElementById('btn-get-quote').addEventListener('click', () => {
const result = calculator.calculate();
const resultText = result.exact
? calculator.formatPrice(result.exact)
: `from ${calculator.formatPrice(result.min)} to ${calculator.formatPrice(result.max)}`;
// Fill hidden form fields
document.getElementById('form-calc-result').value = resultText;
document.getElementById('form-calc-params').value = JSON.stringify(calculator.values);
// Scroll to the form or open a modal window
document.getElementById('quote-form').scrollIntoView({behavior: 'smooth'});
});
The server-side handler saves the calculation parameters in the lead's comments:
$calcParams = json_decode($data['calc_params'] ?? '{}', true);
$paramsSummary = [];
foreach ($calcParams as $paramId => $value) {
$paramsSummary[] = $paramId . ': ' . (is_array($value) ? implode(', ', $value) : $value);
}
$lead = new \CCrmLead(false);
$lead->Add([
'TITLE' => 'Cost calculation — ' . $name,
'COMMENTS' => "Calculation result: {$calcResult}\n" . implode("\n", $paramsSummary),
]);
Saving Calculations
It is useful to save calculations for analytics — which parameters are chosen more often, which values lead to an application:
class CalculatorLeadTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getTableName(): string { return 'b_hl_calc_leads'; }
// CALC_ID, PARAMS_JSON, RESULT_MIN, RESULT_MAX, RESULT_EXACT, SESSION_ID, CONVERTED (bool), CREATED_AT
}
Record on each "Calculate" click, mark CONVERTED = true when a lead is created. Conversion rate = CONVERTED / total records.
Development Timeline
| Option | Scope | Timeline |
|---|---|---|
| Simple calculator | Fixed prices, total, form | 3–5 days |
| Coefficient-based | Base price × coefficients, range | 5–8 days |
| With configurator | Price management via the admin panel | 8–14 days |







