Project Cost Calculator Development for 1C-Bitrix
A project cost calculator is a primary qualification tool for IT companies, agencies, project organisations and studios. It does not give an exact price — that is impossible without delving into the task — but it helps the client understand the order of magnitude and segments incoming enquiries: it filters out non-target requests and warms up serious ones.
Specifics of Project Work Calculation
A project is not a product with a fixed price. The cost depends on:
- The set of functional blocks (what is included in scope)
- The complexity of each block (from standard to custom)
- The technology stack (development speed, licence costs)
- Deadlines (an urgent project costs more)
- Relationship type (one-off development, support, outsourcing)
The calculator structures these variables into a manageable questionnaire.
Data Model: Blocks and Their Costs
Functional blocks are stored in a HighLoad block ProjectBlocks:
| Field | Type | Description |
|---|---|---|
UF_CATEGORY |
Enum | Category (Design, Development, Integration, SEO…) |
UF_BLOCK_NAME |
String | Block name |
UF_COMPLEXITY |
Enum | simple / standard / complex |
UF_HOURS_MIN |
Int | Minimum hours |
UF_HOURS_MAX |
Int | Maximum hours |
UF_HOURLY_RATE |
Float | Hourly rate for this type of work |
UF_IS_REQUIRED |
Bool | Mandatory block (always included) |
UF_DEPENDS_ON |
String | Dependency block ID (cannot be selected without it) |
PHP Calculator with Dependency Resolution
namespace MyProject\Services\Calculators;
class ProjectCostCalculator
{
private array $blocks;
private array $selectedIds;
private float $urgencyCoeff;
private float $marginPercent;
public function __construct(
array $allBlocks,
array $selectedIds,
string $urgency = 'normal',
float $marginPercent = 30
) {
$this->blocks = $allBlocks;
$this->selectedIds = $this->resolveDependencies($selectedIds, $allBlocks);
$this->urgencyCoeff = match ($urgency) {
'urgent' => 1.5,
'fast' => 1.25,
'normal' => 1.0,
'flexible' => 0.9,
default => 1.0,
};
$this->marginPercent = $marginPercent;
}
public function calculate(): array
{
$breakdown = [];
$totalHoursMin = 0;
$totalHoursMax = 0;
$totalCost = 0;
foreach ($this->selectedIds as $blockId) {
$block = $this->findBlock($blockId);
if (!$block) continue;
$hoursMin = $block['UF_HOURS_MIN'];
$hoursMax = $block['UF_HOURS_MAX'];
$rate = $block['UF_HOURLY_RATE'];
$costMin = $hoursMin * $rate * $this->urgencyCoeff;
$costMax = $hoursMax * $rate * $this->urgencyCoeff;
$totalHoursMin += $hoursMin;
$totalHoursMax += $hoursMax;
$totalCost += ($costMin + $costMax) / 2;
$breakdown[] = [
'id' => $blockId,
'name' => $block['UF_BLOCK_NAME'],
'category' => $block['UF_CATEGORY'],
'hours' => "{$hoursMin}–{$hoursMax}",
'cost_min' => round($costMin),
'cost_max' => round($costMax),
];
}
// Add margin
$margin = $totalCost * ($this->marginPercent / 100);
$finalCost = $totalCost + $margin;
return [
'breakdown' => $breakdown,
'hours_min' => $totalHoursMin,
'hours_max' => $totalHoursMax,
'cost_min' => round($finalCost * 0.85),
'cost_max' => round($finalCost * 1.2),
'cost_avg' => round($finalCost),
'urgency_coeff' => $this->urgencyCoeff,
'weeks_min' => ceil($totalHoursMin / 40),
'weeks_max' => ceil($totalHoursMax / 40),
];
}
private function resolveDependencies(array $selectedIds, array $blocks): array
{
$resolved = $selectedIds;
foreach ($blocks as $block) {
if (in_array($block['ID'], $selectedIds, true) && !empty($block['UF_DEPENDS_ON'])) {
$depId = (int)$block['UF_DEPENDS_ON'];
if (!in_array($depId, $resolved, true)) {
$resolved[] = $depId;
}
}
// Required blocks are always included
if ($block['UF_IS_REQUIRED'] && !in_array($block['ID'], $resolved, true)) {
$resolved[] = $block['ID'];
}
}
return array_unique($resolved);
}
}
UX: Checkboxes with Hints
The calculator interface is a set of groups with checkboxes. Each checkbox is accompanied by a brief description: the client understands what is included in the block.
// Update total on every change
document.querySelectorAll('.block-checkbox').forEach(cb => {
cb.addEventListener('change', async () => {
const selected = [...document.querySelectorAll('.block-checkbox:checked')]
.map(el => parseInt(el.value));
const urgency = document.querySelector('[name="urgency"]:checked').value;
const resp = await fetch('/ajax/calculator/project/', {
method: 'POST',
body: new URLSearchParams({
selected: JSON.stringify(selected),
urgency,
sessid: BX.bitrix_sessid(),
}),
});
const data = await resp.json();
updateResultPanel(data);
});
});
Passing the Brief to CRM
The calculation result forms a structured brief:
// Create a deal in Bitrix24 or a lead in the site CRM
$comments = "PROJECT COST ESTIMATE\n\n";
$comments .= "Selected blocks:\n";
foreach ($calcResult['breakdown'] as $item) {
$comments .= "— {$item['category']}: {$item['name']} ({$item['hours']} h.)\n";
}
$comments .= "\nTotal: {$calcResult['cost_min']} – {$calcResult['cost_max']}\n";
$comments .= "Timeline: {$calcResult['weeks_min']} – {$calcResult['weeks_max']} weeks\n";
$comments .= "Urgency: {$urgency}\n";
Development Timelines
| Task | Timeline |
|---|---|
| Basic calculator (10–15 blocks, simple sum, application form) | 5–8 days |
| Calculator with categories, dependencies, coefficients, CRM transfer | 2–3 weeks |
| Calculator with PDF brief, calculation history, A/B tests | 4–6 weeks |
The key rule: a project cost calculator must never give a price lower than the real one. It is better to add a 20–30% buffer and explain it as a "preliminary estimate" than to get a client with inflated expectations.







