Розробка сайту фітнес-клубу на 1С-Бітрікс
Сайт фітнес-клубу — це не вітрина з фотографіями залів. Це робочий інструмент, через який клієнт записується на тренування, купує абонемент, стежить за залишком занять і взаємодіє з тренером. Все це має працювати без ручного втручання адміністратора, синхронізуватися з внутрішніми системами клубу і витримувати пікові навантаження в понеділок вечором, коли половина міста вирішує почати нове життя.
Розберемо, як це реалізується на 1С-Бітрікс — від структури даних до логіки бронювання місць.
Розклад занять: Highload-блоки та фільтрація
Розклад — центральний елемент сайту. Клієнт приходить сюди найчастіше, і якщо розклад гальмує або незручно фільтрується — він йде в Telegram-бот конкурента.
Чому Highload-блок, а не звичайний інфоблок. Звичайний інфоблок (b_iblock_element) зберігає дані в EAV-моделі: кожна властивість — окремий рядок в b_iblock_element_property. При 300 заняттях на тиждень, 15 залах і 40 тренерах таблиця властивостей розростається на десятки тисяч рядків. Фільтрація по комбінації «зал + день + тренер + напрямок» перетворюється на серію JOIN-ів, які на боєвому сервері дають 800-1200 мс.
Highload-блок (b_hlblock_entity) — це плоска таблиця в MySQL/PostgreSQL. Один рядок = одне заняття, усі поля — колонки. Фільтрація працює через звичайні індекси.
Структура Highload-блока FitnessSchedule:
| Поле | Тип | Призначення |
|---|---|---|
| UF_DATE | date | Дата заняття |
| UF_TIME_START | string | Початок (формат HH:MM) |
| UF_TIME_END | string | Закінчення |
| UF_HALL_ID | integer | ID залу (зв'язок з HL FitnessHalls) |
| UF_TRAINER_ID | integer | ID тренера (зв'язок з інфоблоком тренерів) |
| UF_DIRECTION_ID | integer | Напрямок: йога, кросфіт, басейн... |
| UF_CAPACITY | integer | Максимум учасників |
| UF_BOOKED | integer | Поточна кількість записаних |
| UF_STATUS | enumeration | active / cancelled / full |
| UF_IS_RECURRING | boolean | Повторюване за шаблоном |
| UF_TEMPLATE_ID | integer | Посилання на шаблон розкладу |
Для повторюваних занять створюється окремий Highload-блок ScheduleTemplate з полями дня тижня та часу. Cron-завдання (Bitrix agents або системний cron) раз на тиждень генерує конкретні заняття на наступний тиждень за шаблонами. Це дозволяє тренеру скасувати конкретне заняття 15 березня, не ломаючи весь розклад.
Фільтрація на фронту. Використовуємо Bitrix\Highloadblock\HighloadBlockTable::compileEntity() для отримання ORM-сутності, далі стандартний DataManager::getList() з фільтром:
$result = $entityClass::getList([
'filter' => [
'UF_DATE' => $selectedDate,
'UF_HALL_ID' => $hallId,
'UF_STATUS' => 'active',
],
'order' => ['UF_TIME_START' => 'ASC'],
]);
На фронті розклад рендирується як сітка: по горизонталі — зали, по вертикалі — часові слоти. Клієнт перемикає день, фільтрує за напрямком або тренером. AJAX-запити через компонент bitrix:highloadblock.list або кастомний REST-ендпоінт.
Онлайн-запис з лімітом місць і waitlist
Запис на заняття — це не просто «натиснув кнопку, потрапив у список». Це транзаційна операція з перевіркою ліміту, конкурентним доступом і механізмом очікування.
Основний сценарій:
- Клієнт натискає «Записатися» на конкретному занятті
- Система перевіряє:
UF_BOOKED < UF_CAPACITY - Якщо так — створює запис у Highload-блоці
FitnessBooking, інкрементуєUF_BOOKED - Якщо ні — пропонує встати в лист очікування
Проблема конкурентного доступу. Два клієнти одночасно натискають «Записатися» на заняття, де залишилось одне місце. Без блокування обидва отримають підтвердження, а на тренуванні опиниться зайвий чоловік.
Рішення — використання \Bitrix\Main\Application::getConnection() з транзакцією та блокуванням рядка:
$connection = \Bitrix\Main\Application::getConnection();
$connection->startTransaction();
$row = $entityClass::getList([
'filter' => ['ID' => $scheduleId],
'select' => ['UF_BOOKED', 'UF_CAPACITY'],
'runtime' => [/* FOR UPDATE через raw SQL */],
])->fetch();
if ($row['UF_BOOKED'] < $row['UF_CAPACITY']) {
// створюємо бронювання, інкрементуємо UF_BOOKED
$connection->commitTransaction();
} else {
$connection->rollbackTransaction();
// пропонуємо waitlist
}
На практиці чистий ORM Bitrix не підтримує SELECT ... FOR UPDATE, тому критичну секцію обертаємо в raw SQL через $connection->query().
Лист очікування (waitlist). Окремий Highload-блок FitnessWaitlist з полями: UF_SCHEDULE_ID, UF_USER_ID, UF_POSITION, UF_CREATED_AT. Коли хтось скасує запис, агент Bitrix перевіряє waitlist і автоматично переносить першого в черзі в основний список, відправляючи SMS/push через модуль messageservice.
Скасування запису. Клуб зазвичай дозволяє скасування за 2-4 години до початку. Логіка перевірки часу — в обробнику події, який порівнює UF_TIME_START з поточним часом і блокує скасування, якщо ліміт пройшов.
Продаж абонементів через модуль sale
Абонементи фітнес-клубу — це не товари з каталогу. У них своя логіка: термін дії, кількість відвідувань, заморозка.
Типи абонементів реалізуються як елементи інфоблока «Абонементи» з властивостями:
-
DURATION_DAYS— термін дії в днях -
VISIT_LIMIT— ліміт відвідувань (0 = безліміт) -
TYPE— разовий / місячний / річний -
FREEZE_ALLOWED— можна ли заморожувати -
FREEZE_MAX_DAYS— максимальний термін заморозки
При покупці через \Bitrix\Sale\Order::create() абонемент додається в кошик як звичайний товар, але після оплати спрацьовує обробник події OnSaleOrderPaid. Він створює запис у Highload-блоці UserSubscription з полями: UF_USER_ID, UF_START_DATE, UF_END_DATE, UF_VISITS_LEFT, UF_IS_FROZEN, UF_FREEZE_START.
Заморозка абонемента. Клієнт у особистому кабінеті натискає «Заморозити». Система перевіряє FREEZE_ALLOWED і FREEZE_MAX_DAYS, встановлює UF_IS_FROZEN = true, зберігає UF_FREEZE_START. При розморожуванні — перераховує UF_END_DATE, додаючи кількість замороженого часу.
Інтеграція з CRM-системами клубу
Фітнес-клуби рідко працюють тільки через сайт. Основні системи обліку — 1С:Фітнес клуб та Mobifitness.
1С:Фітнес клуб. Обмін через стандартний протокол CommerceML або через REST API самого 1С (HTTP-сервіси). Синхронізуються: довідник послуг, розклад, клієнтська база, продажі абонементів. Обмін запускається по cron кожні 15-30 хвилин.
Mobifitness API. REST API з авторизацією по токену. Основні ендпоінти: /api/v1/schedule, /api/v1/bookings, /api/v1/clients. Bitrix виступає фронтендом, Mobifitness — мастер-системою розкладу. В цьому випадку Highload-блок FitnessSchedule заповнюється не вручну, а через синхронізацію з API.
Вибір архітектури залежить від того, яка система є мастер-системою даних. Якщо клуб уже працює в Mobifitness — сайт підтягує розклад звідти. Якщо клуб переходить на цифру з нуля — Bitrix може бути основною системою.
Особистий кабінет клієнта
Особистий кабінет будується на стандартному модулі main (авторизація, профіль) з розширеннями:
-
Історія відвідувань — вибірка з
FitnessBookingпоUF_USER_IDз JOIN на розклад -
Залишок занять — поле
UF_VISITS_LEFTзUserSubscription -
Продовження абонемента — кнопка, яка створює замовлення в
saleз прив'язкою до поточної підписки - Заморозка/розморозка — інтерфейс керування статусом підписки
Авторизація — через телефон з SMS-кодом (модуль messageservice + кастомний компонент). Фітнес-аудиторія не любить паролі.
Профілі тренерів
Тренери — окремий інфоблок з прив'язкою до напрямків через множинну властивість типу «Прив'язка до елементів». Кожен тренер має: фото, сертифікати (файлові властивості), досвід, опис, посилання на розклад (фільтр по UF_TRAINER_ID).
На детальній сторінці тренера його розклад на поточний тиждень автоматично виводиться — один AJAX-запит до FitnessSchedule з фільтром за тренером.
Терміни реалізації
| Масштаб проекту | Склад | Термін |
|---|---|---|
| Невеликий клуб (1 зал, 5-7 напрямків) | Розклад, запис, абонементи, особистий кабінет | 8-10 тижнів |
| Мережа з 3-5 клубів | + мультисайтовість, єдина база, інтеграція з Mobifitness | 14-18 тижнів |
| Велика мережа (10+ клубів) | + B2B-портал для корпклієнтів, мобільне додаток через REST API Bitrix, складна тарифікація | 20-28 тижнів |
На що звернути увагу
Перед розробкою потрібно визначити мастер-систему розкладу: чи буде розклад ведатися в Bitrix або в сторонній CRM. Це визначає напрямок синхронізації та архітектуру Highload-блоків. Друге питання — платіжний шлюз: для абонементів з автоспісанням потрібен еквайринг з підтримкою рекурентних платежів, а це окрема інтеграція через sale.paysystem.







