CRM для музыкальных школ. Мультитенантный SaaS: одна установка обслуживает много школ, каждая видит только свои данные.
PostgreSQL 17 · FastAPI · React + TypeScript
Существующие CRM для учебных центров делятся на две группы, и обе промахиваются мимо музыкальной школы.
Универсальные (Битрикс24, amoCRM) умеют воронку и бронирование ресурсов, но не знают, что такое абонемент: списание за занятие, заморозку на каникулы, отработку пропуска и зарплату преподавателя там либо считают в Excel, либо заказывают у интегратора. Платформы для онлайн-школ (Antitreningi и подобные) умеют уроки и тесты, но у них нет ни расписания, ни кабинетов, ни посещаемости — офлайн-центру они не подходят вовсе.
В итоге администратор музыкальной школы ведёт расписание в одном месте, остатки абонементов в другом, а зарплату преподавателей в третьем — и сводит это руками.
RockCRM закрывает именно эту работу. Отметка посещаемости — одно действие, которое само двигает остаток абонемента, начисление преподавателю и напоминание о продлении.
| Спецификация | Домен, роли, правила абонемента, границы v1 |
| Контракты этапов | расписание · абонементы · воронка |
| Схема БД | 6 миграций, изоляция тенантов, ограничения целостности |
| Бэкенд | FastAPI на голом SQL, без ORM |
| Фронтенд | React и TypeScript, две рантайм-зависимости |
| Прототип | Кликабельный интерфейс, открывается файлом без сборки |
| Тесты схемы | Проверки гарантий базы на живом PostgreSQL |
Каждый этап начинается с контракта между бэкендом и фронтендом: он
фиксирует форму данных до того, как написана первая строка кода, поэтому
обе стороны пишутся параллельно и не ждут друг друга. Расхождения между
контрактом и реальностью попадают в задачи с меткой пробел-контракта —
их набралось достаточно, чтобы считать этот приём окупившимся.
Абонемент — журнал, а не счётчик. Остаток занятий не хранится изменяемым числом: есть журнал операций, остаток — их сумма. Спор с родителем «куда делось занятие» разрешается открытием журнала, ошибочная отметка гасится компенсирующей записью, а не правкой задним числом.
Правила копируются в абонемент при продаже. Если школа завтра решит, что прогул больше не сгорает, вчерашние абонементы не пересчитаются: проданный абонемент — договор на условиях, действовавших при покупке.
Двойную бронь запрещает база, а не код. Ограничения исключения по кабинету и преподавателю: два администратора, сохраняющие занятие одновременно, оба увидели бы свободный слот, и проверка в приложении здесь бесполезна.
- Спецификация и схема БД
- Прототип интерфейса
- Расписание и посещаемость
- Абонементы: списание и отработки при отметке
- Зарплаты: начисление от проведённого занятия
- Карточка ученика, продажа и продление абонементов, заморозки
- Воронка заявок, приём по вебхуку, отчёт по источникам
- Кабинет родителя
- Мультитенантный онбординг и биллинг
Закрыты три этапа. Расписание и отметка: одно действие списывает занятие, начисляет преподавателю и предупреждает о продлении. Карточка ученика и абонемент: поиск по родителю, история движений, продажа с предпросмотром итога, заморозка со сдвигом срока. Воронка заявок: приём из Telegram-бота и LeadHub, пробный урок в расписании, конверсия в ученика, отчёт по источникам.
Общий принцип экранов — администратор видит последствия прежде, чем нажать: сколько спишется, сколько начислится, до какого числа продлится, сколько занятий отменится.
Текущее состояние проверок: 165 тестов бэкенда и 22 теста схемы против настоящего PostgreSQL. Схема проверяется на том, что она запрещает, а не на том, что таблицы создались.
docker run -d --name rockcrm-pg \
-e POSTGRES_PASSWORD=dev -e POSTGRES_DB=rockcrm \
-p 5432:5432 postgres:17-alpine
for f in db/00*.sql; do psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f "$f"; doneСхеме нужно расширение btree_gist — оно есть в официальном образе
postgres, но отсутствует в урезанных сборках. Подробности
и порядок подключения приложения — в db/README.md.