FastAPIReactPostgreSQLSaaSERP / CRMNDA

Флагманский проект — 2026

RentOS — операционная система для ивент-проката. От пилота в Казани — к международному SaaS

Строим с нуля продукт, которым заменим Bitrix24 и табличный хаос у компаний, сдающих в аренду оборудование для мероприятий: звук, свет, видео, сцены, риггинг. Пилотом стал Stage Group — один из крупнейших прокатов Казани с парком в тысячу с лишним единиц. Дальше — мультитенантный SaaS на РФ+СНГ, отдельный продукт под Европу и западные рынки.

Стек

FastAPI + React

База

PostgreSQL + MinIO

Формат

SaaS-платформа

Статус

Пилот на клиенте

Кейс за 60 секунд

Вместо костылей на Bitrix24 — операционная система, заточенная под ивент-прокат: сделки, сметы, поштучный склад и субаренда в одном месте.

Клиент и боль

Stage Group, Казань — один из крупнейших прокатов event-оборудования в регионе (парк больше тысячи единиц). Bitrix24 не знал, что у каждого микрофона свой серийник и история ремонтов: склад жил отдельно от смет, субаренда — в чатах и Excel, документы терялись.

Что построили

Отдельный продукт под специфику проката: сделки с живой сметой, склад с поштучным учётом и append-only историей (неизменяемая лента событий по каждой железке), автоматическая субаренда при дефиците, публичный доступ подрядчикам по токену. Архитектура сразу под мультитенантный SaaS (один продукт, разные клиенты).

Кому подойдёт

Компании ивент-проката и аренды оборудования, которым Bitrix24 и amoCRM малы, а GetCourse/Tilda не подходят для специфики цикла «сделка → смета → монтаж → возврат».

FastAPIReactPostgreSQLMinIOSaaS / ERP
Посмотреть: под NDA

О клиенте

Stage Group — Казань, event-прокат полного цикла

Основатель — Артур. Под его руководством компания выросла в один из крупнейших прокатов региона с техническим обеспечением мероприятий федерального уровня.

16 лет на рынке

Stage Group работает в Казани с 2010 года. Одна из крупнейших технических баз региона — звук, свет, видео, сцена, риггинг. Парк — больше тысячи единиц оборудования.

Корпорации и звёзды

ВТБ, OZON, Samsung, KION, LG, КАМАЗ, Ростсельмаш, «Ак Барс Банк», концерт Моргенштерна*, УНИКС (закрытие сезона), Break Rumble Kazan, фестиваль «Исток Fest».

Городские праздники

День Молодёжи РТ (10 000 зрителей), День города Казани (9 000), Всероссийская Студенческая Весна, Фестиваль Движения Первых РТ. Федеральный уровень мероприятий.

Международный форум

Международный машиностроительный кластерный форум на «Казань-Экспо» — звук, свет, видео, сцена. Многозальные конференции, синхронный перевод, пресс-зоны.

* Алишер Моргенштерн включён Министерством юстиции РФ в реестр иностранных агентов. Упоминание носит информационный характер и иллюстрирует масштаб мероприятий клиента.

Проблема

Bitrix24 не заточен под прокат — это история про продажи

Готовые CRM построены вокруг цикла «лид → сделка → продажа». В ивент-прокате цикл принципиально другой: сделка живёт неделями, смета меняется до последнего дня, оборудование бронируется, монтируется, возвращается, чинится, списывается. Bitrix24 гнут под это годами — и всё равно упираются в потолок.

Нет понятия «единица оборудования»

В Bitrix24 есть товар и остаток. Для проката этого мало — у каждой микшерной консоли свой серийник, история ремонтов, царапины. Два одинаковых монитора — на самом деле не одинаковые. Bitrix24 не знает, как с этим работать.

Склад не связан со сметами

Менеджер добавляет 12 радиомикрофонов в смету — система не скажет, что 8 уже уехали на другое мероприятие в те же даты. Узнают в день монтажа, когда грузчики обнаружат пустые ящики. Дальше — паника и срочная субаренда.

Субаренда — в табличке в мессенджере

Своего не хватает — зовут партнёров. Кто, что, по какой цене, когда забрать — всё в чатах и Excel. Раз в пару месяцев что-то теряется: партнёру забыли заплатить, оборудование не вернули вовремя.

Смета-конструктор вручную

Смета для ивента живёт в Excel. Пять версий в папке Google Диска, у трёх людей разные. Клиент спрашивает «а что мы согласовали?» — тишина. Excel-генератор сметы написан отдельно, в CRM не интегрирован.

Подрядчику — пачку PDF в почту

Технический директор мероприятия — часто не штатный, а внешний. Чтобы он увидел, что по его ивенту — ему пересылают документы и скриншоты сметы в Telegram. Отдельного интерфейса нет, по NDA пускать нельзя.

Кастомизация упёрлась в потолок

Bitrix24 заточен под стандартный B2B-цикл: лид → сделка → продажа. В прокате цикл другой: сделка → динамическая смета → бронь оборудования → монтаж → демонтаж → возврат → акт. Надстройки на Bitrix24 превратились в лоскутное одеяло.

Решение

Свой продукт под специфику рынка — не костыли на чужой CRM

Почему не Bitrix24 / amoCRM

Можно натянуть воронку на прокатный цикл, пересадить смету в поля сделки, прикрутить склад через Битрикс24.Товары. На выходе — хрупкая надстройка, которую нельзя продать другим прокатам, нельзя масштабировать и нельзя нормально развивать. Кастомизация заморожена — любое обновление платформы может её сломать.

Что мы строим

Отдельный продукт на собственном стеке: FastAPI + React, PostgreSQL + MinIO + Redis. Домен — прокат event-оборудования. Логика сделки, динамическая смета, поштучный склад с append-only историей, автоматическая субаренда при дефиците, публичный доступ для подрядчиков. Архитектура под мультитенантность и compliance сразу — под РФ, СНГ и позже под Запад.

UI пилота

Как выглядит система внутри

Минималистичный светлый интерфейс на шрифте Urbanist с фиолетовым акцентом. Собственный UI-kit, не Ant Design и не шаблонные Material-компоненты. Ниже — четыре ключевых раздела в текущем виде.

Раздел Сделки в CRM RentOS — список сделок с фильтрами по статусам и таблицей
Сделки. Центральная сущность: статусы, фильтры, таблица с названием, типом, контактом, датой мероприятия и ответственным.
Раздел Склад в CRM RentOS — каталог оборудования с фильтрами по категориям
Склад. Каталог оборудования с фильтром по шести категориям. Колонки: категория, цена в день, всего, доступно.
Раздел Субаренда в CRM RentOS — договоры субаренды оборудования
Субаренда. Пять статусов воркфлоу: поиск, согласовано, забрано, возвращено, закрыто. Партнёр, даты, стоимость, заметки.
Раздел Контакты в CRM RentOS — база клиентов и партнёров
Контакты. База клиентов и партнёров. Отметка is_partner, источник лида, телефон, email, компания.

Скриншоты пилотной инсталляции. Тестовые данные — часть настройки среды, не реальные клиенты Stage Group.

Архитектурные решения

Шесть trade-offs, которые определили продукт

Каждое крупное решение — выбор между альтернативами с последствиями на годы. Фиксируется документом рядом с кодом: что выбрали, что отвергли, почему. Ниже — развилки, которые задали облик RentOS.

Физический экземпляр — отдельная сущность, не поле на товаре

Выбрали

Отдельная таблица equipment_unit с FK на equipment

Отвергли

Атрибут serial_number прямо на estimate_item

Почему: Серийник живёт дольше позиции сметы. Радиомикрофон за пять лет побывает в 200 сметах — если хранить серийник в estimate_item, получаем дубликаты и нет единого места истории. Отдельная сущность — один серийник, одна строка, вся история рядом.

Append-only история вместо снапшотов

Выбрали

equipment_unit_history с запретом UPDATE/DELETE на уровне СУБД

Отвергли

Текущее состояние + soft-delete с флагом is_active

Почему: Снапшоты отвечают на «что сейчас», история отвечает на «что было». При споре со страховой (микрофон сломался на ивенте) или клиентом (потерялся за ивент Х) нужна хронология событий, а не текущее состояние. Миграция с одного на другое — переписать половину бэка. Выбор сразу.

quantity_available вычисляется, а не хранится

Выбрали

Запрос к активным сметам в период на каждый вызов

Отвергли

Хранимое поле + триггеры на изменения смет

Почему: Триггеры и очереди на перерасчёт — источник рассинхрона. При сложных правилах (отмена сделки, изменение дат, частичный возврат) рано или поздно хранимое поле расходится с реальностью. Прямой запрос с индексом по (equipment_id, event_start, event_end) — предсказуемо и отлаживаемо. Когда упрёмся в производительность — кешируем через Redis с инвалидацией по сделке, не раньше.

UUID как PK везде, не автоинкремент

Выбрали

uuid4() на всех таблицах

Отвергли

BIGSERIAL / int id

Почему: Мультитенантный SaaS по определению объединяет данные нескольких инсталляций при репликации и бэкапах. На int-id это боль: коллизии, маппинг, offset'ы. UUID — безопасный по умолчанию ID, нельзя угадать или перебрать, сливается между средами без конфликтов. Чуть больше индекса — но проблема далеко за горизонтом этого проекта.

Async-first везде, не sync + gevent

Выбрали

FastAPI + SQLAlchemy 2.0 async + asyncpg

Отвергли

Flask/Django + gevent или thread pool

Почему: 80% работы бэка — I/O: запросы к БД, MinIO, генерация Excel. На sync-фреймворке каждый HTTP-запрос держит поток, потолок масштабирования — сотни параллельных. На async — десятки тысяч на одном воркере. Плюс нативный async — это one fewer magic layer, чем monkey-patching в gevent. Дебажить проще.

Отдельный деплой под Запад, не один тенант на глобус

Выбрали

Две параллельные инсталляции с общим кодом и разным compliance-слоем

Отвергли

Одна база + флаги region на записях

Почему: ФЗ-152 и GDPR — это не «флаг в базе», это где физически крутится сервер и кому принадлежит юрлицо. Одна база для всех юрисдикций — юридическая мина. Общая кодовая база + разные инфры + разные юрлица — единственная конфигурация, которая не лопнет при первой же проверке.

В цифрах

Масштаб платформы

11
сущностей в модели данных
5
ролей пользователей
6
категорий оборудования
1000+
единиц в парке клиента
append-only история
РФ + СНГ
план масштабирования

Инвентарь

Склад как у аэропорта — поштучно, по серийнику, с историей

Главное отличие проката от магазина — каждая единица уникальна. У микшерной консоли есть серийник, история ремонтов, царапина на левой крышке. Система учитывает это на уровне модели данных.

Номенклатура + физический экземпляр

В справочнике хранится «Микрофон Shure SM58» с базовой ценой. А у каждого физического микрофона — свой серийник, история ремонтов, текущая сделка. Для кабелей и стоек экземпляры не нужны — отслеживаем только по количеству. Флаг is_serialized решает всё.

Склад пересчитывается на лету

quantity_available — не хранимое число, а вычисление. Система смотрит на все активные сметы в период запрашиваемых дат и отнимает забронированное. Менеджер в смете сразу видит: в эти даты из 30 радиомикрофонов свободно 12.

Append-only история перемещений

Каждое событие с единицей оборудования — отдельная запись в equipment_unit_history. Никаких UPDATE и DELETE — только append. Карточка единицы — это хронологический список: получено → выдано на ивент Х → возвращено → отдано в ремонт → возвращено из ремонта. Аудит-трейл на всю жизнь железки.

Шесть категорий

sound, light, video, rigging, power, other. Фильтрация по категориям во всех списках: склад, смета, загрузочный лист. Логист за одну секунду видит, что по звуку в этой сделке — не проматывая пять экранов.

equipment_unit_history — append-only
-- Shure SM58 #A12-004891 — полная лента событий
2023-06-12  received           -- пришёл от поставщика
2023-09-04  rented     deal#142  -- ивент «Казань-Экспо», форум
2023-09-07  returned   deal#142
2023-11-18  rented     deal#188  -- День Молодёжи РТ
2023-11-19  returned   deal#188
2024-02-03  sent_to_repair      -- треснул корпус
2024-02-21  repaired            -- заменили корпус
2024-05-10  rented     deal#267  -- корпоративный ивент OZON
-- Никаких UPDATE, никаких DELETE. Только INSERT.

Сценарии из жизни

Что бывает в реальном прокате — и как это закрывает система

Фичи описываются просто, настоящая проверка — edge cases. Восемь сценариев из жизни ивент-проката, каждый из которых ломает типовую CRM и заложен в модель данных RentOS изначально.

1

Клиент в пятницу согласовал смету. В понедельник звонит: «Нам ещё 8 лбов и комплект ферм»

Смета approved — read-only. Система создаёт новую версию сметы с приписанными позициями, старая остаётся в истории с пометкой superseded_by. Клиент видит обе в приложении — можно сверить, что изменилось. Excel перегенерируется и уходит заново.

2

Ивент переносят с 14-го на 28-е. Оборудование под 14-е уже забронировано другим ивентом

Смена event_start/event_end у сделки триггерит пересчёт quantity_available во всех позициях её сметы. Если в новых датах есть дефицит — менеджер видит жёлтые плашки на конкретных позициях. Возможно создать auto-сублизу прямо отсюда, без перехода в раздел.

3

Два ивента в один уик-энд претендуют на один и тот же комплект Meyer Sound

Конфликт ловится в момент добавления позиции во вторую смету. Система не блокирует — показывает, в какой сделке уже забронировано, на чьей ответственности, с какими датами. Менеджеры видят друг друга и решают: приоритет, замена, сублиза.

4

Микрофон упал на ивенте. Звукорежиссёр просит замену из машины

В equipment_unit_history пишется sent_to_repair с ссылкой на сделку. В позиции сметы этот экземпляр заменяется на другой из того же equipment — без изменения суммы. По возвращении в офис — ремонт, или списание, или возврат в активы. Вся цепочка в карточке единицы.

5

Клиент со своим и без НДС — один и тот же прокат, разные юрлица

У companies — разные ИНН и настройка НДС. Смета генерится под юрлицо сделки: в Excel подставляется нужный шаблон, итог с/без НДС. Документы (акт, счёт, договор) тоже рендерятся по шаблонам юрлица.

6

Партнёр по субаренде не вернул 4 радиомикрофона к обещанной дате

Статус sublease не перешёл в returned, return_date просрочена. Логист видит просрочку в сводке. На следующих этапах — автоматические напоминания партнёру по email/SMS, эскалация админу. Пока это ручная проверка, но данные уже лежат так, что автоматизация накручивается без переделок.

7

Технический директор на площадке увидел, что привезли не то оборудование

Открывает страницу сделки в своём приложении (он в роли tech_director), смотрит смету по позициям. Нашёл расхождение — оставляет комментарий в позиции с фото. Менеджер в офисе получает пуш, принимает решение. Всё внутри одной сделки, без звонков и переписок в Telegram.

8

Подрядчик переслал ссылку на страницу ивента коллеге

Токены подрядчика одноразовые на IP не делаем — это неудобно, пусть смотрит с любого устройства. Но токен привязан к одной сделке с TTL. Если есть подозрение на утечку — админ инвалидирует токен одной кнопкой, генерит новый, отправляет подрядчику лично.

Субаренда

Не хватило своего — система сама открыла задачу партнёру

На крупном ивенте 40 радиомикрофонов — нормально. Своих 28. Раньше дефицит замечали в день монтажа — и начинали экстренные звонки коллегам. Теперь дефицит ловится в момент составления сметы и сразу превращается в задачу на субаренду.

1

Менеджер добавляет позицию в смету

Пишет: нужно 40 радиомикрофонов на 3 дня. Система смотрит склад, видит — 28 свободно, 12 не хватает.

2

Автоматическое разделение

quantity_own = 28, quantity_sublease = 12. Позиция сметы остаётся одной строкой, но внутри знает — часть своя, часть под субаренду.

3

Создаётся запись субаренды

Автоматически появляется задача: subleases, привязанная к сделке, со статусом searching. Логист открывает единое окно по сделке и видит — нужно найти 12 микрофонов.

4

Партнёр + цена + даты

Логист выбирает партнёра (компания или контакт с is_partner = true), ставит pickup_date, return_date, cost. Статус меняется: searching → agreed.

5

Забрали — отметили

picked_up. По умолчанию принимаем на доверии (is_checked = false) — это норма для рынка. Если проверили каждую единицу — ставят галочку.

6

Вернули — закрыли

returned → closed. Вся цепочка видна одной лентой по сделке: какое оборудование своё, какое партнёрское, когда и кому отдали, когда вернули.

Сметы

Смета — живой документ, одна на сделку, с Excel на кнопку

В ивент-прокате смета меняется до последнего. Клиент дозаказывает, логист находит более дешёвую замену, менеджер пересчитывает скидку. Старые CRM под это не заточены — поэтому у всех в индустрии сметы живут в Excel. Мы вернули смету внутрь системы.

Одна активная смета на сделку

Constraint на уровне БД: один deal_id → один estimate со статусом не cancelled. Никаких «пяти версий в папке». Меняется смета → меняется она же, с журналом изменений.

Динамические цены и дни

Итог позиции: quantity × price_per_unit × days. Цену менеджер может переопределить вручную (VIP-клиент), дни — посчитать по периоду сделки. Итог сметы пересчитывается автоматически.

Скидка тремя способами

Процент от суммы, фиксированный рубль, или отсутствует. discount_type enum. Никаких «давайте минус 15%» в переписке — скидка в смете, утверждена, зафиксирована.

Утвердили — read-only

Смета со статусом approved не редактируется. Хочешь что-то поменять — создаёшь новую версию, старая остаётся в истории. Принципиально: утверждённая смета = договорное обязательство.

Excel на кнопку

При переходе в approved триггерится генерация Excel-сметы — макет под брендинг клиента, логика итогов, скидок, НДС. Файл кладётся в MinIO, URL пишется в estimate.excel_file_url. Клиенту уходит ссылка.

Загрузочный лист — отдельно

В ивент-прокате смета и загрузочный лист — разные документы. Смета живёт до ивента и меняется. Загрузочный фиксируется за 1–2 дня и содержит серийники конкретных единиц. В roadmap как отдельная сущность — не смешиваем с estimate.

estimate_service.py — итог + скидка
# Итог сметы — пересчитывается при каждом изменении позиции
subtotal = sum(
    item.quantity * item.price_per_unit * item.days
    for item in estimate.items
)

discount = (
    subtotal * (estimate.discount_value / 100) if estimate.discount_type == "percent"
    else estimate.discount_value                 if estimate.discount_type == "fixed"
    else 0
)

estimate.total_amount = subtotal - discount

Подрядчик

Страница мероприятия по ссылке — без логина и пароля

Технический директор ивента — часто внешний исполнитель на один проект. Дать ему аккаунт в CRM нельзя (риски безопасности), пересылать PDF по Telegram неудобно. Решение — публичная страница по уникальному UUID-токену.

Генерация

Токен с TTL

Менеджер нажимает «Сгенерировать ссылку для подрядчика» в карточке сделки. Создаётся UUID, привязывается к deal_id, с датой окончания действия.

Доступ

Без JWT

Эндпоинт /contractor/{token}не требует авторизации. Отдаёт смету, документы, контакты команды, адрес площадки. Только по этой сделке, ничего лишнего.

Безопасность

Отозвать в один клик

Ссылка скомпрометирована — админ инвалидирует токен, генерируется новый. Данные из CRM не попадают в публичный интернет ни на секунду дольше, чем нужно.

Роли

5 ролей — от админа до подрядчика по токену

В прокате участвуют разные люди с очень разным уровнем доступа. Собственник, менеджеры по сделкам, логисты на складе, технические директора на площадке, внешние подрядчики. На каждого — свой набор прав. Матрица доступа — на уровне архитектуры, а не на уровне if-ов в коде.

1

Admin

Полный контроль: пользователи, настройки, все сделки и сметы, склад, субаренда, документы. Для собственника бизнеса и руководителя CRM.

2

Manager

Ведёт сделки и сметы, работает с контактами и компаниями. Видит склад (смотрит, не редактирует), управляет субарендой. Основная рабочая роль.

3

Logistics

Владелец склада: оборудование, экземпляры, история перемещений, субаренда. Сделки видит в read-only — планирует выдачу и возврат.

4

Tech Director

Технический директор мероприятия. Видит только свои сделки и документы по ним. Нужен ему, чтобы на площадке открыть смету и сверить, что привезли.

5

Contractor

Внешний подрядчик без аккаунта. Доступ по уникальной UUID-ссылке к публичной странице своего ивента. Авторизация не нужна, но данные под токеном и с ограниченным сроком действия.

Compliance

ФЗ-152 и аудит — в архитектуре, не поверх неё

Если планируешь масштабировать продукт на РФ и СНГ, а потом отдельно на Европу — compliance должен быть заложен с первой строки кода. Доделывать потом будет в разы дороже и долго.

ФЗ-152 из коробки

Данные физлиц (контакты, сотрудники, подрядчики) хранятся только на серверах в РФ. Это требование заложено в архитектуру, не прикручено поверх — трансграничной передачи персональных данных в пилоте не происходит.

Разделение слоёв

Под европейский рынок — отдельное юрлицо и отдельная инсталляция с соблюдением GDPR. Код общий, инфраструктура и юридическая обёртка — разные. Никакой «мы тут одну базу для всех сделали».

MinIO как замена S3

Self-hosted объектное хранилище. Договоры, акты, Excel-сметы, фото повреждений — всё в MinIO внутри периметра клиента. AWS/Google в пилоте не используется.

Append-only аудит

equipment_unit_history не допускает UPDATE и DELETE — только запись. В любой момент можно доказать: что, когда, где, кто. Полезно при спорах со страховыми и клиентами.

Модель данных

11 сущностей — все UUID, все связи строгие

UUID как первичный ключ везде — нельзя угадать или перебрать. Внешние ключи с ON DELETE RESTRICT на сделках и сметах — случайно удалить историю оборудования невозможно. Миграции через Alembic, откат на любую версию одной командой.

contacts

Физлица — клиенты, технические директора, сотрудники партнёров. ФИО, телефон, email, компания, отметка is_partner.

companies

Юрлица — клиенты и партнёры по субаренде. Название, ИНН, контактное лицо, is_partner.

deals

Сделки. Тип: rental / order. Статусы: new → in_progress → approved → in_work → done. Ответственный менеджер + технический директор ивента.

estimates

Сметы. Одна активная на сделку (DB-constraint). Статусы: draft → sent → approved. Процентная/фиксированная скидка. Ссылка на Excel в MinIO.

estimate_items

Позиции сметы. Количество своё + количество под субаренду. Статус бронирования: preliminary → confirmed → in_use → returned.

equipment

Справочник оборудования. Категория, цена в день, базовое количество, флаг is_serialized для поштучного учёта.

equipment_unit

Физический экземпляр — только для is_serialized = true. Марка, модель, серийник (UNIQUE), статус, текущая сделка, дата покупки.

equipment_unit_history

Лента событий по экземпляру. Только append. Типы: received, rented, returned, sent_to_repair, repaired, written_off. Карточка единицы собирается из этой таблицы.

sublease

Субаренда. Партнёр (компания или контакт), статусы searching → agreed → picked_up → returned → closed, даты, стоимость, флаг is_checked.

documents

Файлы по сделке и смете. Типы: contract, invoice, act, excel_estimate, other. Файлы в MinIO, в БД только ссылка.

users

Пользователи CRM со связкой на роль. JWT + refresh token, bcrypt-хэши паролей, ротация сессий, логаут из всех устройств.

API

Чистый REST на FastAPI — async, Pydantic, OpenAPI

Бэкенд строится как набор узких, async-first сервисов с чёткой слоистой архитектурой. Фронт на React + TypeScript тянет типы из OpenAPI — несоответствий между бэком и фронтом физически не возникает.

REST под Pydantic v2

Все запросы и ответы — Pydantic-схемы. Автовалидация, 422 при несоответствии, OpenAPI-документация из коробки. Фронт подтягивает типы без дублирования.

async на всём

FastAPI + SQLAlchemy 2.0 async + asyncpg. Сервер не блокируется на запросах к БД. Одного воркера хватает на десятки одновременных пользователей.

Слоистая архитектура

router → service → repository → model. Роутеры знают только про HTTP, сервисы — про бизнес-правила, репозитории — только про запросы к БД. Переписать endpoint — не задевает бизнес-логику.

Alembic-миграции

Каждое изменение схемы — отдельный файл с версией. Как git, только для базы. Откатиться на любую точку — одна команда. Никаких «я тут поменял ALTER TABLE на проде».

JWT + RBAC

Access-токен 15 минут, refresh 30 дней в httpOnly-cookie. На каждый endpoint — декоратор: какой ролью можно. 5 ролей, матрица доступа на уровне архитектуры.

Токены подрядчика

Для страницы мероприятия — отдельный тип токена. Привязан к deal_id, с TTL, без JWT-паролей. Отдельный роут /contractor/{token}, авторизация не требуется.

Методология

От интервью до продакшна — восемь этапов

Продукт такой сложности нельзя собрать «по ТЗ на страницу». Нужна структура: от разговора с людьми, которые каждый день делают смету, до пилота на живых данных. Процесс фиксирован и воспроизводим для каждой новой фичи.

1

Интервью с бизнесом

Не «какой UI вы хотите», а «покажите, как вы реально работаете». День на складе с логистом. Час с менеджером, пока он составляет смету. Разговор с техническим директором о том, что он делает на площадке. Терминология записана ровно так, как её произносит индустрия — загрузочный лист, позиция, сабик, линейка.

2

Маппинг процессов в модель данных

Каждая бизнес-сущность становится таблицей или полем. Не наоборот. Если процесс в жизни звучит «одна смета на сделку, после утверждения не правится» — это DB-constraint и enum status. Если «оборудование бывает уникальное и обезличенное» — это флаг is_serialized и вторая таблица. Модель не навязывает бизнесу свои правила.

3

Техспек с обоснованием решений

Перед кодом — документ. Архитектура, схема БД, state-машины, API-контракт, RBAC-матрица, угрозы безопасности. Каждое крупное решение — с альтернативой и причиной выбора. Клиент читает и кивает (или возражает) до первой строки кода. Проект не сваливается в переделки на поздних этапах.

4

Декомпозиция на атомарные задачи

Техспек режется на десятки узких задач: «контакты — CRUD», «сделки — статусы и переходы», «сметы — утверждение + Excel», «equipment_unit — история». Каждая задача — с приёмочными критериями. Не «сделать красиво», а «endpoint возвращает 422 при таком-то входе, 200 — при таком-то».

5

TDD на бизнес-логике

Для сервисного слоя — тесты пишутся до кода. «Создание сметы на сделку, где уже есть активная смета» → ожидаем exception. «Утверждение сметы с пустыми позициями» → ожидаем 400. Реализация затыкает тесты, а не наоборот. Это экономит месяцы отладки на живых данных клиента.

6

Код-ревью и архитектурные проверки

Каждая фича прогоняется через отдельный проход: безопасность (OWASP Top 10), консистентность со спекой, утечки PII в логах, корректность N+1-запросов, здоровье миграций. Реальные баги лезут не из-за незнания, а из-за спешки. Отдельный шаг отлова этого — дешевле, чем инциденты на проде.

7

Пилот на реальных данных

MVP разворачивается на клиенте. Работает рядом с Bitrix24 — менеджер ведёт сделку и там, и там, пока не убедимся в паритете. Расхождения — в бэклог. Только когда ни одного расхождения на нескольких сделках подряд — рубим старое и переезжаем полностью. Никакого «большого переключения».

8

Продуктовая итерация

После пилота — выжимка паттернов, которые повторились у клиента. То, что заложено сразу в архитектуру. То, что специфично для Stage Group и идёт в конфигурацию тенанта. Продуктовое и клиентское разделяется на этом этапе — чтобы SaaS-версия не несла на себе чужую историю.

Мобильные приложения

Следующая волна — нативные приложения для iOS и Android

Веб-админка закрывает офис. Но ивент-бизнес живёт на площадке и на складе — где ноутбук не открыть и 4G не тянет. Нативные приложения в планах сразу после стабилизации MVP — с офлайн-режимом и синхронизацией по приходу связи.

iOS + Android для менеджера

В поле: открыть сделку, сверить смету с тем, что привезли, отметить позиции как in_use / returned, сфотографировать повреждённую единицу и приложить к equipment_unit_history. Push-уведомления о смене статуса сделки, вопросах от клиента, дефиците на складе.

iOS + Android для логиста

Сканер штрихкодов на складе: камерой считал код — единица оборудования у тебя в руках, статус меняется на rented. Возврат — аналогично. Поиск по серийнику голосом. Карточка единицы с историей перемещений — свайпом вверх.

Страница подрядчика в браузере телефона

Веб-страница по токену уже адаптивна — отдельное приложение подрядчикам не нужно. Но для менеджеров и логистов нативные приложения с офлайн-режимом критичны: на ивенте 4G лагает, приложение должно работать, а потом досинхронизироваться.

Дорожная карта

От одного клиента — к международному продукту

RentOS задуман не как разовая CRM под одну компанию. Stage Group — пилот, на котором обкатывается логика. Следующие этапы — мультитенантный SaaS под русскоязычный рынок и отдельный продукт под западные юрисдикции.

1

Фаза 1 — Пилот

Stage Group, Казань

Развёртывание на клиенте, обкатка бизнес-правил, доработки по фидбеку. Проверяем, что логика проката корректна на реальных сделках и реальном складе.

2

Фаза 2 — РФ + СНГ

Multi-tenant SaaS

Сеть прокатов в городах-миллионниках России, Беларусь, Казахстан. ФЗ-152 уже учтён в архитектуре — данные физлиц на российских серверах. Мультитенантность: одна инсталляция, разные рабочие пространства.

3

Фаза 3 — Европа и Запад

Отдельное юрлицо и бренд

Отдельная компания под европейский и западный рынок. GDPR, локальные платёжные шлюзы, английский UI, мультивалютность, европейские дата-центры. Кодовая база общая — конфигурации и compliance-слой свои.

Технический стек

Что внутри

Backend

  • Python 3.12 + FastAPI (async-first)
  • SQLAlchemy 2.0 async + asyncpg
  • Alembic (migrations)
  • Pydantic v2 (validation)
  • Celery + Redis (фоновые задачи)
  • JWT + RBAC (5 ролей)

Frontend

  • React 19 + TypeScript
  • Vite (быстрая сборка)
  • Zustand (state)
  • React Query + Axios (HTTP + кэш)
  • React Hook Form + Zod (формы)
  • Urbanist + собственный UI-kit

Storage & Infra

  • PostgreSQL 16 (основная БД)
  • Redis 7 (кэш + очереди)
  • MinIO (S3-совместимое хранилище)
  • Docker Compose (dev + prod)
  • Nginx reverse-proxy + SSL
  • Серверы в РФ (ФЗ-152)

Безопасность

  • JWT access (15 мин) + refresh (30 дн)
  • bcrypt-хэши паролей
  • UUID как PK (не угадать)
  • Append-only аудит (equipment_unit_history)
  • Rate limiting на /api/auth
  • Токены подрядчика с TTL

Кто делает

За кейсом стоит инженер, а не автогенератор

Качество архитектуры определяет не инструмент, а человек. Ниже — как устроена команда, как принимаются решения и как выстроен процесс вокруг реального кода.

Кто ведёт проект

Ярослав Хорев — sole developer + decision maker в этом проекте. 10+ лет коммерческой разработки, собственные запущенные продукты (LookBot, Tonema, LMS для квест-бизнеса), кейсы с крупными брендами (Додо пицца, Спортмастер, GeekBrains, МФТИ). Полный стек: от схемы БД и архитектуры до фронта, DevOps и общения с клиентом.

Как принимаются решения

Архитектурное решение — документ с альтернативами, trade-off и причиной выбора. Хранится в репозитории проекта рядом с кодом. Клиент видит, за что платит, почему именно так, и что было отвергнуто. Через полгода можно вернуться и понять — почему мы тогда пошли этим путём.

Как мы работаем с AI

Современный инструментарий — часть процесса: автодополнение, генерация тестов, статический анализ, ревью миграций. Но AI не пишет архитектуру, не принимает решения о модели данных и не общается с клиентом. Эти шаги делает разработчик, и именно за них платят. AI ускоряет рутину — не заменяет инженерное мышление.

Инфраструктура процесса

Приватный репозиторий в GitHub, Conventional Commits, pre-commit хуки (gitleaks на секреты, линтеры), Alembic-миграции с ревью перед накатом, тесты в CI, отдельный dev-VPS для интеграционной отладки. Не «пушим на прод и молимся» — структурированный процесс с безопасными точками отката.

Итог

Из «Bitrix24 не тянет» — в собственный SaaS с международным горизонтом

Клиент просил заменить CRM. Мы предложили построить продукт — с моделью данных под прокат, поштучным складом, автосубарендой, публичными страницами для подрядчиков и compliance под ФЗ-152. Всё, что нужно большому ивент-бизнесу, в одном периметре.

Результат — платформа, которая обкатывается на Stage Group и дальше пойдёт продуктом. Сначала мультитенантный SaaS для прокатов РФ и СНГ, потом отдельная компания под европейский и западный рынок. Один из самых амбициозных проектов в нашем портфолио.

Упёрлись в потолок готовой CRM?

Если Bitrix24, amoCRM или 1С не закрывают специфику вашего бизнеса — обсудим, что можно построить своё. Прокат, логистика, производство, любая отраслевая модель с нестандартным циклом сделки.

Написать в Telegram