Мы продаём инженерные узлы для отопления и водоснабжения многоэтажек. Каждая сделка — расчёт под конкретный дом, коммерческое предложение, договор, закуп, сборка и отгрузки партиями по графику стройки. Ещё недавно эта цепочка жила в Битрикс24, отдельном калькуляторе, 1С и рабочих чатах.
Мы написали свою CRM. Раньше от заявки до подписанного договора проходило около двух месяцев, теперь — около месяца: сделки закрываются примерно вдвое быстрее. Сильнее всего сократился первый отрезок: раньше КП уходило заказчику через 3–4 дня после заявки, теперь — на следующий день. Ниже — где на самом деле терялось время, что мы для этого сделали и на какие грабли наступили.
Кто мы и что продаём
Наш товар нельзя взять с полки — его сначала нужно посчитать. Заказчик — застройщик жилого комплекса. Он покупает коллекторные узлы отопления на каждый этаж, этажные узлы холодной и горячей воды и трубную разводку по квартирам.
Узел собирается под этаж: число квартир задаёт число выходов, дальше следуют диаметры, балансировка, бренд арматуры. Один дом — это десятки узлов и тысячи фитингов: в одном реальном КП было 84 узла, в другом — 4 078 ниппелей. Заказчик видит в КП готовый узел с шифром, а снабжению и складу нужна каждая деталь внутри.
Ещё три обстоятельства, без которых дальше не понять:
Три юрлица. Два ТОО продают, третье ведёт монтаж. Учёт — в 1С.
Поставка партиями. Договор живёт месяцами: график отгрузок идёт за стройкой и меняется вместе с ней.
Много рук. Менеджер, инженер, коммерческий директор, юрист, руководство, снабженец, склад, водитель, бухгалтер. В системе сейчас 12 ролей, и почти каждая трогает сделку хотя бы раз.
Где терялось время
Время уходило не на работу, а на ожидание между работами. Расчёт, КП, согласование — каждая работа сама по себе недолгая. Дни уходили на то, чтобы каждый узнал: сейчас его ход.
До своей CRM сделка жила в четырёх местах:
Битрикс24 — сделки и контакты. У инженеров, снабжения и склада там своего места не было.
Калькулятор инженеров — отдельное браузерное приложение. Состав и сумма из него переезжали в сделку и в КП.
1С — счета, реализации, поступления. Данные сделки вносили туда ещё раз.
Чаты — всё остальное: «посчитай», «согласуй», «что с договором?», «когда отгрузка?».
Отсюда три вида потерь.
Ожидание на передаче. Менеджер ждёт расчёт, а инженер не знает, что его ждут. КП готово, но коммерческий директор увидит его, когда долистает чат. Каждая передача — это ожидание, а передач до первой отгрузки семь.

Восемь шагов и семь передач. До CRM ни одна передача не сигналила сама: следующий узнавал о своём ходе из чата или звонка.
Перенос руками. Калькулятор → сделка → КП → 1С. Каждый перенос — время и шанс ошибиться. В одном КП из старого калькулятора 12 узлов ушли по 194 ₸ вместо 262 763 ₸ за штуку. Недобор — 3,15 млн ₸, около 15% суммы, а в строке «ИТОГО» стояло 68 узлов вместо 84. Причина нашлась позже: калькулятор молча ставил 0 ₸ детали без привязки к каталогу.
Никто не знает, у кого сделка. Чтобы ответить на «на чём застряли?», руководитель обзванивал людей. Стадия в Битриксе показывала, где сделка была вчера, а не чей сейчас ход.
Почему своё, а не коробка
Коробочная CRM знает сделку и товар, но не знает узел. А у нас всё держится на узле: заказчик покупает его целиком, снабжение закупает по деталям, склад собирает и отгружает партиями по графику стройки. Состав коллекторного узла выводится из параметров этажа по девятнадцати связанным правилам. Допиливать это в Битрикс роботами и смарт‑процессами значило бы держать бизнес‑логику в чужой системе и потом переносить её дважды.
Мы записали один принцип и держимся его: наша база — источник истины для всего, что не является бухгалтерией. Сделки, расчёты, КП, договоры, графики, закуп и склад живут у нас. В 1С остаются номенклатура, цены и бухгалтерские документы. Битрикс и 1С подключены через адаптеры, и любой можно выключить без потери ядра.
Из Битрикса уходим поэтапно: сначала зеркалим его сделки, потом синхронизируем в обе стороны, потом сами становимся мастером. Критерий перехода между этапами — не технический: месяц без жалоб «а в Битриксе было удобнее». Резкий переезд убил бы продажи: менеджеры там живут.
Стек скучный, и это специально:
Слой |
Чем сделан |
|---|---|
Бэкенд |
PHP 8.3, Laravel 13, модули по предметным областям (CRM, продажи, калькулятор, закуп, склад, исполнение, интеграции и др.) |
База |
PostgreSQL, около 116 таблиц |
Фронтенд |
Vue 3, PrimeVue 4, Vite |
Телефон |
PWA с Web Push — без отдельного мобильного приложения |
Интеграции |
1С по OData, Битрикс24 по REST и вебхукам |
Тесты |
около 1 600 автотестов + сквозной прогон по ролям в браузере |
Что сделали
Мы били по передачам. Цель — чтобы человек узнавал, что ход за ним, в ту же минуту и попадал на экран, где нужная кнопка уже под рукой.
1. «Мой день» вместо чатов
Рабочий день начинается с экрана «Мой день». На нём только карточки дел, которые ждут именно этого человека: «Ждут вашего решения», «Новый расчёт», «Узлам не хватает деталей» и так далее. Дела других людей, за которыми руководителю надо присматривать, убраны в свёрнутый блок «Присмотр», чтобы «моё» не тонуло в «чужом».
Карточка ведёт не в сделку вообще, а на вкладку с нужной кнопкой. Это проверяет робот в браузере: до правки сразу на нужную кнопку вели 3 карточки из 8, после — 7 из 8.
2. Пуш на каждое новое дело
CRM ставится на телефон как PWA, отдельного приложения нет. Раз в минуту сервер собирает «Мой день» каждого сотрудника и сравнивает со снимком. Появилось новое дело — уходит Web Push на iPhone или Android.
Две детали, без которых это не работает:
Каждая строка карточки отдаёт стабильный id: сущность плюс номер, а для события — ещё и состояние. Иначе сравнение снимков либо молчит, либо спамит.
За один проход — не больше пяти пушей на человека, остальное сворачивается в «ещё N». Первый проход после включения только снимает снимок, иначе человек получит пуш на каждое старое дело.
3. «Что дальше» в шапке сделки и договора
Под шапкой сделки одна строка в одном из трёх состояний: «сделайте вот это», «ждём такого‑то» или «готово». На вопрос «у кого сделка» теперь отвечает экран, а не телефон. Правило интерфейса: подпись кнопки в этой строке слово в слово совпадает с подписью на вкладке, потому что человек ищет глазами ту же надпись.
4. Расчёт → спецификация → КП без перепечатки
Калькулятор теперь внутри CRM. Менеджер запрашивает расчёт прямо из сделки: раздел, срок «нужен к», пояснение. Инженер получает карточку и пуш. Готовый расчёт он замораживает версией, и дальше всё читает этот снимок: спецификация, КП в PDF, потом закуп и сборка.
Узел продаётся как товар с шифром. Заказчик видит в КП готовый узел, а снабжение из того же снимка видит каждую деталь и чего не хватает по каждому узлу. Ручной перенос исчез — а вместе с ним и узлы по 194 ₸.
5. Три способа получить работу — намеренно
Мы не стали сводить всё к одной схеме «назначить ответственного». У разных ролей работа приходит по‑разному, и система это повторяет:
Кто |
Как получает работу |
Почему так |
|---|---|---|
Менеджер |
закреплён за сделкой, передаёт главный менеджер |
сделка длинная, у заказчика один собеседник |
Инженер |
расчёт назначает главный инженер |
загрузку знает только он; сам инженер выберет проект по вкусу |
Снабжение, склад |
берут сами кнопкой «Беру» |
раздавать заявки по одной некому, а справится любой |
6. Решения в одном месте
Согласование КП и договора — одна карточка «Ждут вашего решения» и две кнопки на всех экранах: «Согласовать» и «Вернуть». Вернуть можно только с причиной — иначе автор идёт в чат спрашивать «а что не так?», и передача снова висит.
7. Заявка по ссылке
Первую передачу мы отдали заказчику. Менеджер отправляет ссылку, заказчик сам заполняет объект и контакты, и сделка появляется в CRM уже с данными — без переписывания из письма.
Откуда «вдвое»
Отрезок |
Было |
Стало |
|---|---|---|
От заявки до КП у заказчика |
3–4 дня |
около 1 дня |
От заявки до подписанного договора |
около 2 месяцев |
около 1 месяца |
Это оценка команды по сделкам, а не строгий замер. И оговорка: «на 50% меньше времени» — это и есть «в два раза быстрее». А «на 50% быстрее» значило бы в полтора раза.
КП ускорилось в 3–4 раза, а весь цикл — в два. Разница понятна: дальше по цепочке много времени уходит на сторону заказчика — переговоры, его юристов, его бюджет. На это CRM почти не влияет.
Как сделаем строгий замер. Медиана дней от создания сделки до подписания договора. Проигранные и тестовые сделки не считаем. «До» берём из истории Битрикса, «после» — из журнала CRM. Медиана, а не среднее: одна сделка, которая полгода ждёт финансирования застройщика, сдвигает среднее сильнее любых наших правок.
Чего эти цифры не доказывают. Система работает недавно, сделок через неё прошло пока немного, а стройка сезонная. Семь правок мы выкатывали подряд, поэтому какая из них дала больше всего, не знаем. Через квартал посчитаем по журналу и напишем, подтвердилось ли.
Грабли
«Готово» в дорожной карте — не значит «работает»
На аудите выяснилось, что сделка ни разу не прошла цепочку целиком. Все пункты дорожной карты стояли с галочками, тесты были зелёными. Но тесты звали сервер с параметрами, которых интерфейс не шлёт, и тупики прошли мимо CI. Из интерфейса сделка доходила до КП, а после договора в базе не было ни одной записи.
Помогли две вещи. HTTP‑тесты пишем «как шлёт интерфейс», а не «как удобно тесту». И регулярно гоняем сквозной прогон по ролям в браузере, с подменной 1С. Первый такой прогон дал 15 находок. В боевой базе есть галочка «Прогон»: такая сделка живёт на отдельном складе и в 1С ничего не отправляет.
1С по OData врёт с кодом 200
Все пять ловушек мы нашли на живой базе, а не в документации:
Страницы без
$orderbyприходят в произвольном порядке. Из 4 550 позиций справочника дошло 1 177 уникальных. При этом строк было ровно 4 550, и с$countвсё сходилось. Сверяйте число уникальных ключей, а не строк.If-Matchигнорируется. PATCH с чужой версией отвечает 200 и пишет. Версию сверяем сами чтением в том же сеансе.Длины режутся молча. Номер документа — 11 знаков, и «ОТ-2026-1000» станет «ОТ-2026-100», обновив чужой документ. В
$metadataдлин нет, мы добыли их опытом.Сумма документа не пишется. PATCH отвечает 200, значение остаётся нулём.
Даты уходят без зоны. Платформа читает присланное смещение как своё.
Поэтому правила обмена жёсткие. Метода DELETE в клиенте нет физически. Документы создаём только непроведёнными — проводит бухгалтер. Перед любой правкой спрашиваем, не проведён ли документ. Оплаты только читаем, регистры не трогаем. Запись в боевую 1С пока выключена.
Артикул — не ключ
Номенклатуру из 1С мы сначала сопоставляли по артикулу. Оказалось, что один артикул стоит у 52 разных позиций: тройники, гильзы, угольники. При первом приёме 197 карточек свернулись в 115 наших позиций. Ошибки в коде не было — негодной оказалась сама предпосылка. Теперь артикул — подсказка для человека, а не тождество. Проверка в одну строку: число непустых артикулов против числа различных.
Гонка на остатках
Склад списывал остаток по схеме «прочитал — проверил — записал» без блокировки. Две одновременные выдачи одной позиции обе проходили проверку и затирали друг друга. Транзакция не спасает: уровень READ COMMITTED в PostgreSQL от такой потери не защищает. Такое расхождение всплывает только на инвентаризации. Закрыли SELECT … FOR UPDATE на строке остатка.
Два калькулятора не сверить чтением кода
Новый калькулятор должен был считать как старый, только без его ошибок. Два дня мы сверяли исходники построчно и пропустили 9 расхождений: значения по умолчанию, умножение количеств, связи между полями. Сработало другое: гонять оба движка на одних данных и сравнивать результат. Эталоном стало реальное КП: 23 шифра узлов с ценами, новый движок восстанавливает все 23. Именно так и нашлись те самые узлы по 194 ₸.
Итоги
Сделки ускорила не какая‑то одна функция. Их ускорило то, что каждый теперь видит свой следующий шаг, узнаёт о нём с телефона и делает его в одно нажатие.
Если вы думаете о своей CRM для сложного товара, вот что мы бы сделали с самого начала:
Нарисуйте передачи, а не стадии. Стадия говорит, где сделка. Передача говорит, кто её сейчас держит. Время теряется на втором.
Начинайте с экрана «что мне сегодня делать», а не со списка сделок. Список сделок нужен руководителю. Исполнителю нужен список дел.
Один источник данных от расчёта до отгрузки. Замороженная версия расчёта кормит и КП, и закуп, и сборку. Любой ручной перенос рано или поздно даст свои 194 ₸.
Тестируйте сделку целиком, глазами каждой роли. Зелёный CI и галочки в дорожной карте ничего не говорят о том, дойдёт ли сделка до отгрузки.
В 1С пишите непроведённым и не верьте коду 200. Проверяйте записанное обратным чтением.
Дальше в планах: счета поставщиков и согласование оплат в закупе, лиды из WhatsApp и полный уход из Битрикса. Через квартал вернёмся с точным замером. Вопросы про архитектуру, 1С и калькулятор — в комментарии.
nurix
Пункт про OData без $orderby прямо узнал. У меня в боте похожее было с рассылкой: обычная постраничная выборка тихо теряла получателей, потому что рассылка сама меняла статусы пользователей и список сдвигался. Никаких ошибок, всё "сходится", просто часть людей ничего не получила. Вылечил курсором по id. Так что совет сверять уникальные ключи, а не число строк, очень в тему.
По гонке на остатках: а не рассматривали атомарный UPDATE вместо FOR UPDATE? Что-то вроде
UPDATE stock SET qty = qty - :n WHERE id = :id AND qty >= :nи проверка, что затронута одна строка. Читать перед записью вообще не нужно, и блокировка держится минимально. Хотя если перед списанием там ещё какая-то логика, то FOR UPDATE, наверное, честнее.И вопрос про пуши: раз в минуту собирать "Мой день" для каждого сотрудника не тяжело по базе? Или при 12 ролях и небольшом штате это вообще незаметно, и о событийной модели думать рано?