Development of a logistics company website using 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
    1173
  • 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
    745
  • 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

Logistics Company Website Development on 1C-Bitrix

A logistics company website is not a brochure — it is an operational tool. A shipper arrives with a specific task: calculate the cost from point A to point B, place an order, track a shipment. If the calculator returns nothing, tracking shows no status, and reordering requires filling out 12 fields from scratch — the client moves to a competitor with a functioning portal. Bitrix can handle all of this, but the architecture must account for logistics-specific workflows from day one.

Service Catalog: Info Blocks for Transport Types

A logistics provider doesn't sell one product — it offers a matrix of services: FTL (full truckload), LTL (less than truckload), warehousing, customs brokerage, last-mile delivery. Each type carries its own constraints — weight limits, dimensions, temperature requirements, geography.

Data structure:

  • Info block "Services" — sections: Road Freight, Sea Freight, Rail, Air, Warehousing, Customs
  • Highload block "Vehicle Types" — reference: curtainside, refrigerated, 20' container, 40' container, insulated. Fields: UF_NAME, UF_CAPACITY_KG, UF_VOLUME_M3, UF_PHOTO, UF_DESCRIPTION
  • Highload block "Routes" — city-to-city pairs linked to transport types. Fields: UF_FROM_CITY, UF_TO_CITY, UF_TRANSPORT_TYPE, UF_TRANSIT_DAYS, UF_ACTIVE
  • Highload block "Cargo Constraints" — max weight, dimensions, prohibited categories per transport type

Each service element contains:

Property Type Purpose
SERVICE_TYPE L (list) FTL / LTL / Warehousing / Customs / LastMile
TRANSPORT_TYPES S:Highload (multiple) Linked vehicle types
ROUTE_DIRECTIONS S:Highload (multiple) Available routes
MAX_WEIGHT N Max cargo weight, kg
MAX_VOLUME N Max volume, m³
TEMPERATURE_MODE L Ambient / Chilled / Frozen
INSURANCE_AVAILABLE L Yes / No
CUSTOMS_INCLUDED L Yes / No

For SEO, the high-value pages follow the pattern "[transport type] + [route]": "Road Freight Moscow to Novosibirsk", "LTL Shipping from China". These pages are generated from the junction of the services info block and the routes Highload block via a custom component. URLs follow the template /services/{service-code}/{from}-{to}/, with human-readable slugs configured through CIBlockElement::GetList filtering on ROUTE_FROM and ROUTE_TO properties.

Freight Calculator: The Primary Conversion Point

The calculator is why 70% of visitors land on a logistics website. Not a "leave your number, we'll call back" form — a real calculation: origin, destination, cargo parameters, price. If the calculator doesn't return a number, the visitor never converts.

Calculator architecture has three layers.

Layer 1 — frontend: multi-step form. Step 1: origin and destination (city autocomplete via AJAX, data from a "Cities" Highload block or an external API like Yandex.Geocoder). Step 2: cargo parameters — weight, volume (L×W×H), number of packages, packaging type, temperature requirements. Step 3: additional options — insurance, customs clearance, door-to-door delivery. Step 4: result — price, transit time, available transport modes.

The form is built as a React component or vanilla JS with step navigation. Each step triggers server-side AJAX validation. City autocomplete hits a dedicated endpoint /api/cities/suggest/?q=Mosc, querying b_hlbd_cities (the Highload block) via DataManager::getList() with a filter ['%UF_NAME' => $query].

Layer 2 — distance calculation. Tariffs depend on distance. Two approaches:

Approach A — precomputed distance matrix in a DistanceMatrix Highload block. Fields: UF_FROM_CITY_ID, UF_TO_CITY_ID, UF_DISTANCE_KM, UF_TRANSIT_HOURS. With 500 cities, that's 250K rows — manageable. Advantage: instant response, no external API dependency. Drawback: needs recalculation when cities are added.

Approach B — real-time calculation via an external API. Google Distance Matrix API or Yandex.Routing API (router.route()). Request: two points, response: distance in km plus travel time. Results are cached in the Highload block — if a city pair already exists and is less than 30 days old, return from cache; otherwise, make the API call and store the result.

// Distance retrieval with caching
class DistanceService
{
    public static function getDistance(int $fromCityId, int $toCityId): array
    {
        $cached = DistanceMatrixTable::getList([
            'filter' => [
                'UF_FROM_CITY_ID' => $fromCityId,
                'UF_TO_CITY_ID' => $toCityId,
                '>UF_CACHED_AT' => date('Y-m-d', strtotime('-30 days'))
            ]
        ])->fetch();

        if ($cached) {
            return [
                'distance_km' => $cached['UF_DISTANCE_KM'],
                'transit_hours' => $cached['UF_TRANSIT_HOURS']
            ];
        }

        $result = GoogleDistanceMatrixClient::calculate(
            Cities::getCoordinates($fromCityId),
            Cities::getCoordinates($toCityId)
        );

        DistanceMatrixTable::add([
            'UF_FROM_CITY_ID' => $fromCityId,
            'UF_TO_CITY_ID' => $toCityId,
            'UF_DISTANCE_KM' => $result['distance'],
            'UF_TRANSIT_HOURS' => $result['duration'],
            'UF_CACHED_AT' => new DateTime()
        ]);

        return $result;
    }
}

Layer 3 — tariff engine. Tariffs live in a Tariffs Highload block:

Field Type Description
UF_SERVICE_TYPE list FTL / LTL / Express
UF_TRANSPORT_TYPE reference Vehicle type
UF_DISTANCE_FROM number Range start, km
UF_DISTANCE_TO number Range end, km
UF_RATE_PER_KM number Rate per km
UF_MIN_RATE number Minimum charge
UF_WEIGHT_COEFF number Overweight multiplier
UF_VOLUME_COEFF number Volume multiplier

Calculation formula for LTL: max(distance_km * rate_per_km, min_rate) * weight_coeff * volume_coeff + insurance + customs_fee. For FTL it is simpler: flat rate per km multiplied by distance, no weight coefficients — the entire truck is booked.

Managers update tariffs through the Highload block admin interface — no developer involvement. This is a hard requirement: tariffs change weekly, and if updating a price requires a deployment, the system is unusable.

Calculator response returns as JSON:

{
  "variants": [
    {
      "transport": "Curtainside 20t",
      "service": "FTL",
      "price": 1200,
      "currency": "USD",
      "transit_days": 3,
      "distance_km": 1800
    },
    {
      "transport": "LTL Consolidated",
      "service": "LTL",
      "price": 340,
      "currency": "USD",
      "transit_days": 7,
      "distance_km": 1800
    }
  ]
}

Below the result — a "Place Order" button that carries all calculation parameters into the order form. The user does not re-enter data.

Shipment Tracking

Tracking is the second reason clients return to the site. A tracking number input on the homepage, result — a status chain with timestamps and current location on a map.

The data source is 1C:TMS or 1C:Logistics. Integration via Bitrix REST API: 1C pushes status updates through POST /rest/logistics.shipment.updateStatus with fields tracking_number, status_code, location, timestamp. Bitrix stores statuses in a ShipmentStatuses Highload block.

Frontend — an AJAX request by tracking number. The response contains an array of statuses (accepted, at warehouse, in transit, at customs, delivered) and coordinates for the last known location, rendered on Google Maps or Yandex.Maps.

Real-time updates — polling every 60 seconds, or WebSocket if traffic volume justifies the complexity.

Client Portal: The B2B Dashboard

A personal dashboard for corporate clients is what separates a serious logistics provider from a "brochure site with a calculator." It is not just order history — it is a working tool for a logistics manager.

Authentication and roles. A client company registers as a legal entity. Within the company — multiple users with distinct roles. Implementation through Bitrix user groups (CGroup) and custom fields:

  • Company admin — sees all orders, manages users, downloads documents, views financials
  • Logistics manager — creates orders, tracks statuses, downloads waybills and CMR notes
  • Accountant — document access only: invoices, acts, tax invoices

User-to-company binding — via a custom field UF_COMPANY_ID in b_user. Access control — middleware in init.php that checks the user's group and UF_COMPANY_ID on every request to /personal/.

Portal functionality:

Order history — a list of all company shipments, filterable by date, status, route. Data from the Orders Highload block: UF_ORDER_NUMBER, UF_COMPANY_ID, UF_FROM_CITY, UF_TO_CITY, UF_STATUS, UF_CARGO_DESCRIPTION, UF_WEIGHT, UF_VOLUME, UF_PRICE, UF_CREATED_AT. Pagination via bitrix:system.pagenavigation, filtering via AJAX.

Document management — each order carries a set of documents: waybill, CMR, invoice, packing list, insurance certificate. Files are stored as multiple file-type properties linked to the order. Downloads go through a custom controller that verifies the document belongs to the requesting user's company before serving the file. No direct links to /upload/ — authorized access only.

// Document access control
class DocumentController extends Controller
{
    public function download(int $orderId, int $fileId): Response
    {
        $user = $GLOBALS['USER'];
        $order = OrdersTable::getById($orderId)->fetch();

        if ($order['UF_COMPANY_ID'] !== $user->getUfCompanyId()) {
            throw new AccessDeniedException();
        }

        $file = CFile::GetFileArray($fileId);
        return new BinaryFileResponse($file['SRC']);
    }
}

Recurring shipment templates. A regular client ships the same cargo on the same routes repeatedly. The logistics manager saves an order as a template — next time, they select the template, adjust the date, confirm. Templates are a separate ShipmentTemplates Highload block mirroring the order structure plus UF_TEMPLATE_NAME and UF_COMPANY_ID.

Financial section — mutual settlements balance, issued invoices, payment history. Data is synchronized from 1C via REST API on a schedule (every 15 minutes) or event-driven.

1C:TMS Integration

Order synchronization between the website and 1C:TMS (or 1C:Fleet Management) is bidirectional:

  • Website to 1C: a new order from the site is sent to 1C via REST API. The 1C endpoint accepts JSON with order parameters and creates a "Transport Request" document
  • 1C to website: a status change in 1C triggers a webhook to Bitrix. The handler updates UF_STATUS in the Orders Highload block and sends an email/SMS notification to the client

Exchange format — JSON over HTTP REST. XML exchange via CommerceML is overkill for logistics — that format is designed for commerce, not freight.

Coverage Map and Fleet

Interactive map — Google Maps or Yandex.Maps with a custom layer. Warehouse and hub markers from the Warehouses Highload block (fields: UF_NAME, UF_ADDRESS, UF_COORDINATES, UF_TYPE, UF_PHOTO). Route lines between hubs — google.maps.Polyline (or ymaps.Polyline) with data from the routes Highload block. Clicking a hub opens an info window with address, working hours, and available services.

Fleet section — an info block with vehicle types. Vehicle card: photo, payload capacity, cargo volume, type (curtainside, refrigerated, container carrier). Rendered via bitrix:news.list with a custom template — a card grid with characteristic icons.

Partner API

REST API for integration with partner systems: freight forwarders, marketplaces, client ERP systems. Endpoints:

  • POST /api/v1/orders/create — order creation
  • GET /api/v1/orders/{id}/status — order status
  • GET /api/v1/tracking/{number} — shipment tracking
  • POST /api/v1/calculate — cost calculation

Authentication — API key in the X-Api-Key header. Keys are generated in the admin panel and bound to a partner company. Rate limiting — 100 requests per minute via middleware.

Stages and Timelines

Scale Timeline
Brochure site with calculator, up to 10 routes 4-6 weeks
Corporate site with portal, tracking, 1C integration 10-16 weeks
Platform with partner API, B2B portal, full automation 16-24 weeks

Timelines exclude 1C-side exchange configuration — that is a separate workstream handled by a 1C developer, running in parallel.