Development of Reports Module for 1C-Bitrix
Standard 1C-Bitrix reports cover basic needs: sales by period, warehouse balances, user activity. But as soon as a task appears like "show conversion funnel by manager for a quarter filtered by region" — the standard tools reach their limit. Here you need a module with custom aggregation logic.
Problem with Native Reports
In Bitrix, reports are scattered across modules: CRM (crm), e-commerce (sale), warehouse (catalog). Data is not aggregated between modules. There is no cross-module report builder. No way to save arbitrary selections and schedule delivery.
Module Structure
The module vendor.reports registers via the standard installer. Main entities:
-
ReportTable (
b_vendor_report) — report registry: id, name, type, config (JSON with selection parameters), created_by, access_groups -
ReportScheduleTable (
b_vendor_report_schedule) — automatic generation schedule: report_id, cron, format, recipients, last_run -
ReportSnapshotTable (
b_vendor_report_snapshot) — cache of generated data: report_id, data (jsonb/text), generated_at, expires_at -
ReportColumnTable (
b_vendor_report_column) — column configuration: report_id, alias, source, aggregation, format
Data Sources
Each source implements DataSourceInterface:
interface DataSourceInterface
{
public function getFields(): array;
public function fetchData(array $filter, array $select, array $group): array;
}
Built-in sources:
-
SaleOrderSource— data fromb_sale_order,b_sale_order_props,b_sale_basket -
CrmLeadSource— fromb_crm_lead,b_crm_lead_field_multi -
CrmDealSource— fromb_crm_deal,b_crm_deal_stage -
CatalogProductSource— fromb_iblock_element,b_catalog_price,b_catalog_store_product -
UserActivitySource— fromb_user,b_user_auth_log,b_stat_adv_back
Sources can be combined via JOIN strategy:
$builder = new ReportQueryBuilder();
$builder
->from('SaleOrderSource', 'o')
->join('CrmDealSource', 'd', 'o.UF_CRM_DEAL = d.ID')
->select(['o.DATE_INSERT', 'o.PRICE', 'd.STAGE_ID', 'd.ASSIGNED_BY'])
->filter(['>=o.DATE_INSERT' => $dateFrom, '<=o.DATE_INSERT' => $dateTo])
->groupBy(['d.ASSIGNED_BY', 'd.STAGE_ID']);
Aggregations and Computed Fields
The module supports standard aggregates: COUNT, SUM, AVG, MIN, MAX. Computed fields are described with expressions:
// Lead to deal conversion percentage
'conversion' => [
'expression' => 'ROUND(COUNT(deals.ID) * 100.0 / COUNT(leads.ID), 2)',
'label' => 'Conversion, %',
'type' => 'percent',
]
The resulting SQL is formed via QueryBuilder with parameter binding — no direct string concatenation.
Visualization
Data is sent to the frontend as JSON via AJAX endpoint:
GET /bitrix/components/vendor/reports.view/ajax.php?report_id=12&date_from=2024-01-01&date_to=2024-03-31
On the browser side — Chart.js or Highcharts (depending on project license). Supported types: line chart, bar chart, pie chart, table with sorting and pagination.
Tables are rendered via standard CAdminList in the admin section or via custom component on site frontend.
Export
The generated report is exported to:
-
Excel (
.xlsx) viaPhpSpreadsheetlibrary — installed in/local/vendor/ - CSV with choice of delimiter and encoding
-
PDF via
mPDForTCPDF— for reports with fixed layout
$exporter = ReportExporterFactory::create('xlsx');
$exporter->setData($reportData)->setColumns($columns)->download('report_' . date('Y-m-d') . '.xlsx');
Schedule and Delivery
The agent \Vendor\Reports\Agent\ScheduleAgent::run() runs hourly, checks b_vendor_report_schedule for records with next_run <= NOW(). Generates the report, saves snapshot to b_vendor_report_snapshot, sends recipients email with attachment via \Bitrix\Main\Mail\Event::send().
Cron expression format is standard-compatible: 0 8 * * 1 — every Monday at 8:00.
Access Control
Report access is managed via Bitrix user groups. The access_groups field in b_vendor_report stores a JSON array of group IDs. Check:
$userGroups = $USER->GetUserGroupArray();
$reportGroups = json_decode($report['ACCESS_GROUPS'], true);
if (!array_intersect($userGroups, $reportGroups) && !$USER->IsAdmin()) {
throw new AccessDeniedException('No access to report');
}
Caching
Heavy reports (data for a year, millions of rows) are cached in b_vendor_report_snapshot. Cache lifetime is set in settings for each report. On request, the snapshot with expires_at > NOW() is checked first; if present, it's returned from cache. Force clear — via button in admin interface or on data change event (OnAfterSaleOrderUpdate).
Development Timeline
| Stage | Duration |
|---|---|
| Architecture, ORM tables, installer | 2 days |
| Data sources (3-4 Bitrix modules) | 3 days |
| QueryBuilder, aggregations, computed fields | 2 days |
| Visualization (Chart.js + table) | 2 days |
| Excel/CSV export | 1 day |
| Schedule and email delivery | 1 day |
| Access control, caching | 1 day |
| Admin interface, testing | 2 days |
Total: 14 working days. Complex cross-module reports with non-standard sources are estimated separately after data structure analysis.







