Розробка портала для логістичної компанії
Логістичний портал — це не просто веб-сайт-візитка з формою заявки. Це робочий інструмент, через який менеджери клієнтів відстежують вантажі, диспетчери призначають машини, водії отримують маршрути, а бухгалтерія вивантажує накладні. Кожна з цих ролей працює зі своїми даними, своїми сценаріями, своїм рівнем доступу.
Функціональні блоки портала
Типовий портал для транспортно-логістичної компанії складається з кількох незалежних модулів, з'єднаних через спільну базу даних і API.
Особистий кабінет клієнта — реєстрація, історія замовлень, поточний статус доставки, документи (CMR, TTH, рахунки), запит на нову перевезення. Клієнт бачить тільки свої дані без чужих вантажів.
Панель диспетчера — вхідні заявки, призначення водіїв і транспортних засобів, відстеження позицій на карті в реальному часі, зміна статусів, чат з водієм.
Мобільний додаток водія (або PWA) — поточне замовлення, маршрут, зміна статусів (забрав/у дорозі/доставив), фотофіксація при видачі, підпис отримувача на екрані.
Адміністративна панель — управління довідниками (міста, тарифи, типи транспорту), звіти, управління користувачами.
Відстеження вантажів: технічний стек
Реальне положення транспортних засобів — один з ключових елементів. Є кілька підходів:
GPS-трекери з власним сервером. Пристрої типу Teltonika FMB920 відправляють координати за протоколом MQTT або через власний TCP-сервер. Дані приходять кожні 30–60 секунд:
# Приклад обробки вхідних даних з GPS-трекера через MQTT
import paho.mqtt.client as mqtt
import json
from datetime import datetime
def on_message(client, userdata, message):
data = json.loads(message.payload.decode())
vehicle_id = data['device_id']
lat = data['lat']
lng = data['lng']
speed = data['speed']
ts = datetime.fromtimestamp(data['timestamp'])
# Зберігаємо у TimescaleDB (PostgreSQL з розширенням для часових рядів)
db.execute("""
INSERT INTO vehicle_positions (vehicle_id, lat, lng, speed, recorded_at)
VALUES (%s, %s, %s, %s, %s)
""", (vehicle_id, lat, lng, speed, ts))
# Публікуємо в Redis для оновлення карти в реальному часі
redis.publish(f'vehicle:{vehicle_id}', json.dumps({
'lat': lat, 'lng': lng, 'speed': speed
}))
Мобільний додаток з геолокацією. Водій включає відстеження через браузер або додаток. Дешевше в інфраструктурі, але залежить від заряду телефону та наявності Інтернету.
Інтеграція зі зовнішніми системами. Yandex.Transport, Wialon, Omnicomm — готові платформи моніторингу з API. Підключення до їх API дозволяє отримувати дані про позицію без власного сервера телеметрії.
Карта в реальному часі
Для відображення позицій використовується WebSocket — сервер штовхає оновлення клієнту без опитування:
// Фронтенд: підключення до WebSocket і оновлення маркерів на карті
const socket = new WebSocket('wss://api.example.com/ws/dispatch');
socket.addEventListener('message', (event) => {
const { vehicleId, lat, lng, speed, status } = JSON.parse(event.data);
if (markers[vehicleId]) {
markers[vehicleId].setLatLng([lat, lng]);
markers[vehicleId].setPopupContent(
`<b>${vehicleId}</b><br>Швидкість: ${speed} км/ч<br>Статус: ${status}`
);
} else {
markers[vehicleId] = L.marker([lat, lng])
.addTo(map)
.bindPopup(`<b>${vehicleId}</b>`);
}
});
Для карти — Leaflet з плитками від OpenStreetMap (безкоштовно) або Yandex.Maps / Google Maps (платно, але краще геокодування для СНГ).
Розрахунок вартості перевезення
Тарифікація у логістичних компаній складна: залежить від ваги, обсягу, відстані, типу вантажу, терміновості, страховки. Рекомендується вивести логіку в окремий сервіс:
class FreightCalculator
{
public function calculate(FreightRequest $request): FreightQuote
{
$distance = $this->distanceMatrix->calculate(
$request->originCity,
$request->destinationCity
);
$baseRate = $this->rateRepository->findRate(
$request->cargoType,
$request->vehicleType,
$distance->zone
);
$weightCharge = max($request->weight, $request->volumetricWeight()) * $baseRate->perKg;
$distanceCharge = $distance->km * $baseRate->perKm;
$insurance = $request->declaredValue * 0.002; // 0.2%
$total = ($weightCharge + $distanceCharge + $insurance)
* $request->urgencyMultiplier()
* $this->seasonalCoefficient();
return new FreightQuote(
base: $weightCharge + $distanceCharge,
insurance: $insurance,
total: round($total, 2),
currency: 'RUB',
validUntil: now()->addHours(24),
);
}
}
Документообіг
Транспортна накладна, CMR, експедиторська розписка — все це повинно генеруватися автоматично з даних замовлення. Використовуються бібліотеки типу TCPDF або Snappy (wkhtmltopdf) для PHP, або Puppeteer для Node.js.
Підпис отримувача збирається через Canvas API в браузері і зберігається як зображення, прикріплене до накладної:
const canvas = document.getElementById('signature-pad');
const signaturePad = new SignaturePad(canvas, {
backgroundColor: 'rgb(255, 255, 255)',
penColor: 'rgb(0, 0, 0)',
});
document.getElementById('save-signature').addEventListener('click', () => {
if (!signaturePad.isEmpty()) {
const dataUrl = signaturePad.toDataURL('image/png');
// Відправляємо на сервер разом з підтвердженням доставки
fetch('/api/deliveries/' + deliveryId + '/confirm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ signature: dataUrl, confirmed_at: new Date().toISOString() }),
});
}
});
Інтеграції зі зовнішніми системами
Логістичний портал рідко живе ізольовано. Типові інтеграції:
- 1С — вивантаження накладних, синхронізація контрагентів, завантаження платежів
- Diadoc / SBIS — електронний документообіг, підписання документів ЕЦП
- Транспортні біржі (ATI.SU, Deliver) — автоматична публікація заявок на перевезення
- Страхові компанії — оформлення страхування вантажу через API
Продуктивність у масштабі
Коли в системі тисячі активних перевезень, наївні запити до БД починають гальмувати. Кілька конкретних рішень:
Геопросторові індекси в PostgreSQL з розширенням PostGIS:
CREATE INDEX idx_vehicle_positions_location
ON vehicle_positions USING GIST (ST_SetSRID(ST_MakePoint(lng, lat), 4326));
-- Вибір усіх машин у радіусі 50 км від точки
SELECT vehicle_id, lat, lng
FROM vehicle_positions vp
JOIN (
SELECT vehicle_id, MAX(recorded_at) as last_seen
FROM vehicle_positions GROUP BY vehicle_id
) latest ON vp.vehicle_id = latest.vehicle_id AND vp.recorded_at = latest.last_seen
WHERE ST_DWithin(
ST_SetSRID(ST_MakePoint(lng, lat), 4326)::geography,
ST_SetSRID(ST_MakePoint(37.6173, 55.7558), 4326)::geography,
50000
);
Розділення таблиці позицій за датою — через місяць дані архівуються і не заважають основним запитам.
Терміни розробки
Мінімально життєздатний портал (особистий кабінет клієнта + статуси + документи) — 6–8 тижнів. Повна система з диспетчерською картою, мобільним додатком водія, розрахунком тарифів і інтеграціями — 4–6 місяців. Це реальні цифри для команди з трьох розробників.







