ERP под ключ
ENRU
Кейс - заказная ERP

Полная ERP с теорией ограничений Голдратта в коде.

Полный бэк-офис для селлера домашнего текстиля на Wildberries: 763 карточки, 4 бренда, около 6 000 заказов в день, производство в Узбекистане с циклом ~60 дней. Система охватывает: досорт на буферах ТОС, ежедневная таблица РНП, 7 типов алертов, дашборд экономики с прогнозом месяца, комбинаторика наборов, ABC-анализ, заказ поставщику в один клик, а с этого года ещё сборку заказов FBS со сканером кодов маркировки и справочник складов.

FastAPIPostgreSQL 16CeleryReact 18 WB APIAPI МойСкладAPI TrueStatsGoogle Sheets
20
модулей бэкенда, у каждого свой README
227
API-эндпоинтов портала
1 166+40
автотестов бэкенда и фронта, прогон каждый день в чистой БД
763
SKU под управлением, 4 бренда
~6 000
заказов в день проходит через систему
3 API
WB + МойСклад + TrueStats, плюс Google Sheets
01 Контекст и боль

Три системы, личный Excel владельца и плечо поставки 60 дней

Быстрорастущий селлер полотенец, халатов и текстильных наборов. Цифры жили в трёх несвязанных сервисах. Главное решение бизнеса, что и когда досортировывать, принималось вручную.

Данные в 3 системах

Кабинет маркетплейса: заказы и реклама. МойСклад: склады и составы наборов. TrueStats: юнит-экономика. Плюс ручные выгрузки файлами. Единой картины не видел никто.

Досорт держался на одном человеке

Какой SKU везти на склад маркетплейса, сколько и когда: владелец решал это ежедневно, в личном Excel, по 763 карточкам в ~27 категориях. Процесс не масштабировался и не переживал отпуск.

Сезонный товар, длинные плечи

Производство занимает около 60 дней, плюс ~10 дней доставка, плюс внутренние плечи. Заказал поздно - пропустил сезон. Заказал рано - заморозил деньги в остатках. Права на ошибку почти нет, а в сезон его нет совсем.

Проблемы замечали глазами

Обнуление остатков, падение заказов, негативные отзывы и расхождения данных всплывали, когда кто-то случайно посмотрел. При 6 000 заказов в день "случайно посмотрел" - это не система контроля.

02 Архитектура

Ночной конвейер: три API на входе, решения на выходе

Каждую ночь Celery-задачи стягивают маркетплейс, МойСклад и TrueStats в PostgreSQL. Расчётные модули превращают сырые строки в буферы, алерты и прогнозы. Люди работают в React-портале или в Google-таблице, которую бэкенд рендерит для менеджеров.

НОЧНОЙ КОНВЕЙЕР ДАННЫХ · ВРЕМЯ МСК Wildberries API заказы · продажи · остатки карточки · реклама · отзывы поставки · тарифы синк 03:30 МойСклад 9 складов · 1 464 товара 349 составов наборов синк 06:30 TrueStats 98 метрик юнит-экономики версии себестоимости синк 08:00, гейт T-1 Ручные файлы аналитика продавца CSV остатков производителя drag-and-drop, маппинг Слой синков конвейер Celery Beat идемпотентные импорты: повторный прогон = 0 изменений гейты свежести: сбой звена = стоп цепочки rate-limit на каждый API watchdog каждые 5 минут журнал каждого прогона PostgreSQL 16 62 таблицы · 12 миграций Redis 7: кэш + брокер бэкапы: 14 дневных + 8 недельных Расчётные модули зоны буфера DBM 08:30 контракт достаточности 7 типов алертов · ABC комбинаторика наборов 08:40 дашборд + прогноз React SPA 17 маршрутов · 10 тем виртуализация таблиц Google-витрина рендер для менеджеров запись только RAW правки назад каждые 10 мин Алерты + Excel 7 типов алертов в ленте фирменный экспорт везде Страница Здоровье автотесты каждый день 05:00 светофоры: контейнеры, память, диск, бэкапы итоги тестов + статус хоста каждые 5 мин
Внешние системы Сервисы ERP Хранилище Самонаблюдение

Одна ночь из жизни системы

7Docker-сервисов за nginx
62таблицы, 12 миграций, одна голова
14backend-модулей, у каждого свой README
33Kстрок Python + TypeScript
10тем интерфейса на CSS-токенах, ноль захардкоженных цветов

Контракты между модулями означают, что ничего не считается дважды: достаточность даёт одна функция, светофор 33/67 живёт в одной настройке, классы ABC отдаёт один сервис. РНП, Google-витрина и алерты потребляют одни и те же цифры.

03 Флагманский модуль

Теория ограничений Голдратта, работающая в проде

Заказчик попросил проверить его формулы досорта и предложить улучшения. Его классический расчёт покрытия остался в таблице видимыми колонками. Поверх система ведёт динамическое буферное управление (DBM): у каждого SKU есть целевой буфер, буфер делится на зоны-светофор, и буфер сам корректируется по фактическому потреблению. Это ТОС по учебнику, реализованная кодом и тестами.

B (целевой буфер) = V_план × N дней × 1.1 × M  ·  V_план = V30 × тренд clamp(V3/V30, 0.5..2.0) × Kсезон

×1.1 - это запас "на паранойю" от заказчика на каждый буфер, в духе Голдратта. M - множитель DBM: стартует с 1.00 и меняется шагами по 1/3.

Зелёная · от 67%

Буфер заполнен достаточно. Ничего не делать. Двенадцать зелёных дней подряд предложат уменьшить буфер: капитал не должен спать в остатках.

Жёлтая · 33-67%

Планировать пополнение. SKU попадает в досорт-таблицу с рассчитанным количеством, а не с догадкой.

Красная · ниже 33%

Заказывать сейчас. Красные дни копятся в 12-дневной истории; красная серия запускает рекомендацию увеличить буфер.

Чёрная · нет товара

Остаток ноль при живых продажах. Потерянная выручка каждый день. Чёрная считается красной в сериях и кричит с верха ленты алертов.

Каждый день в 08:30, сразу после ночных синков пересчитать зону каждого SKU, дописать день в историю зон Окно 12 дней (3 × плечо пополнения WB в 4 дня) посчитать красные и зелёные серии; рекомендации только на полной истории Буфер мал 80% и более из 12 дней в красной или чёрной зоне рекомендация: буфер +1/3 Буфер велик 12 зелёных дней подряд пол: никогда ниже 1/3 рекомендация: буфер -1/3 Кулдаун: 12 дней тишины после любого изменения система никогда не гоняется за собственным хвостом Подтверждает человек: кнопка ±1/3 в строке таблицы автоприменение существует, но по умолчанию ВЫКЛЮЧЕНО: по Голдратту изменение буфера утверждает человек новый M Целевой буфер SKU B = V_план × N × 1.1 × M V_план = скорость × тренд × сезон ×1.1 паранойя · M шагами по 1/3 заполненность P = (остаток + в пути) / B 100% 67% 33% 0 зелёная · ок жёлтая · планировать красная · срочно чёрная · нет товара зоны красит единый светофор системы 33/67
Ежедневная задача DBM Ветка увеличения Ветка уменьшения / контроль человека Стабилизатор Математика буфера

Константы, и почему они именно такие

КонстантаЗначениеОбоснование
Окно DBM12 днейТри плеча пополнения склада маркетплейса (4 дня). Короче - реакция на шум, длиннее - реакция с опозданием.
Триггер увеличения≥80% красных из 12Буфер, живущий в красной зоне, буфером не является. Рекомендация: +1/3 от целевого.
Триггер уменьшения12 зелёных подрядБуфер, не выходящий из зелёной зоны, - это замороженные деньги. Рекомендация: -1/3, и считается только непрерывная серия.
ПолM ≥ 1/3Множитель никогда не падает ниже трети. Если шаг пробил бы пол, уменьшение даже не предлагается.
Кулдаун12 днейПосле любой корректировки система молчит целое окно: сначала измерить эффект одного решения, потом предлагать следующее.
Паранойя×1.1Правило заказчика: +10% к каждому буферу. Оформлено именованной константой, закрыто тестами.
Плечи поставки4 / 14 / 74 дняДо склада маркетплейса / со склада производителя / с производства (цикл 60 дней + 10 доставка + внутренние плечи).
ТОП-правило90%Правило заказчика: SKU класса A с покрытием ниже 90% всегда прыгает в верх досорт-таблицы, выше любого SKU класса C, и досортировывается минимум до 90%.

Сезонность поверх буфера

Ручное сильнее автоматики

У каждой категории есть окно сезона и коэффициент. Ручной коэффициент заказчика всегда приоритетнее автоподсказки. Подсказки считаются из прошлогодних заказов: суммы по неделям, сглаживание MA-3, сезон = сегмент выше 50% пика.

Несезонный запас в штуках

Вне сезона скоростная формула игнорируется. Категория держит фиксированный запас в штуках, распределённый по SKU пропорционально их доле в заказах за 90 дней. Совсем нет истории - поровну.

Плановый дефицит на выходе

В закрывающем окне сезона буфер линейно снижается к запланированному дефициту. Сезон заканчивается пустыми полками по плану, а не остатками на год вперёд.

Человек в контуре

Система рекомендует. Решает человек.

Автоприменение корректировок буфера по умолчанию выключено. Ежедневная задача лишь пишет рекомендацию в строку: кнопка ±1/3 с подсказкой, почему. Менеджер нажимает, изменение логируется, стартует кулдаун. Это дисциплина самого Голдратта: буферы адаптируются, люди утверждают.

Досорт-таблица: зоны-светофор буфера, бейджи ТОП, кнопка DBM плюс-минус треть и sticky-блок заказа поставщику
Досорт-таблица: светофор зон у каждого SKU, бейджи ТОП, кнопка DBM ±1/3, спарклайны со всплесками выше μ+2σ и sticky-блок заказа поставщику справа.
2 387строк в модуле ТОС
22REST-эндпоинта
9своих таблиц
17тестов на правила: пол, кулдаун, сезон через Новый год
33/67один светофор для буферов и алертов
04 Ежедневная аналитика

РНП на 763 SKU и дашборд экономики с прогнозом месяца

РНП, "Рука на Пульсе", - ежедневная панель по каждому SKU: воронка продаж, экономика рекламы, остатки и цены, около 60 колонок на SKU, с виртуализацией, чтобы 763 строки листались как шёлк. Дашборд отвечает на вопрос владельца: чем закончится месяц, если едем как едем?

Таблица РНП

763 строки × ~60 колонок со sticky-колонкой SKU и настраиваемыми светофорами. Карточный режим для работы по исключениям. Каждый SKU раскрывается в карточку: продажи, реклама, остатки, цены и комментарии в окнах 7 / 14 / 30 дней.

Умный импорт файлов

Выгрузка воронки маркетплейса прилетает drag-and-drop. Колонки матчатся нечётким маппингом; переименованная колонка вызывает диалог подтверждения, а не тихое неверное чтение. Битые строки отклоняются поштучно, файл целиком не падает.

Сверка источников

Выручка TrueStats и маркетплейса сравнивается ежедневно. Расхождение больше 5% поднимает баннер. В проде это поймало реальные 13,31% на первом же боевом прогоне.

Дашборд экономики

Экономика кабинета и категорий из файла, API или их слияния по SKU. Прогноз конца месяца по тренду 15 дней. Расходные метрики инвертируют окраску: растущая реклама красная, а не зелёная. Ответ: 61 мс с кэшем, 101 мс без.

Приём внедрения

Менеджеры остались в своей Google-таблице

Бэкенд рендерит витринные листы прямо в привычную таблицу: главная, сезон, распределение, новые цены, контроль остатков, алерты. Запись только RAW, чтобы штрихкоды не превращались в 2.05E+12. Ручные правки возвращаются дифф-пуллом каждые 10 минут, и рендер никогда не затирает ячейку человека. Кнопка в таблице отправляет новые цены назад через вебхук с проверкой токена.

РНП: виртуализированная таблица SKU со светофорами и карточным режимом
РНП: карточный режим по умолчанию, полная виртуализированная таблица в один клик, светофоры с порогами, которые заказчик настраивает сам.
Дашборд экономики с прогнозом месяца и фильтрами по категориям
Дашборд: экономика кабинета, разбивка по категориям, прогноз конца месяца по тренду 15 дней, фильтры с пересчётом на лету.
~60колонок на SKU в РНП
13,31%реальное расхождение, пойманное сверкой
61 / 101 мсответ дашборда, с кэшем / без
10 минцикл дифф-пулла ручных правок таблицы
05 Контур контроля

Семь типов алертов и ERP, которая тестирует себя каждое утро

Проблемы должны сами находить людей, а не ждать, пока их найдут. Семь детерминированных типов алертов следят за бизнесом; страница здоровья со светофорами следит за самой системой.

АлертТриггерДетали
Крайняя срочка≤100 штук на маркетплейсеВсегда красный, всегда закреплён вверху ленты.
Низкая достаточностьпокрытие ниже порогаТот же контракт достаточности, что и в досорт-таблице. Одна формула, одна правда.
Падение заказовскорость за 3 дня против 10 дней, ≥30% внизЛовит умирающий SKU за дни, а не за недели.
Всплеск заказовскорость за 3 дня резко вверхВсплеск - тоже сигнал досорта: продавать, пока летит.
Негативные отзывыновые отзывы на 1-3 звездыПопап с текстами отзывов, серверная пагинация. На приёмке в базе было 1 447 негативных.
Старт производства<90 дней запасаОтсчитывает 60-дневный цикл производства назад от прогнозного обнуления, вне сезона смягчается.
Расхождение данныхразрыв TrueStats и WB >5%Система не доверяет своим входам раньше, чем предложит доверять своим выводам.

Эпизодный жизненный цикл

Алерт - это эпизод: активен, решён, реактивирован в тот же день, если условие вернулось. Ноль дублей по построению. Отметка "видел" - на каждого пользователя, чужое "видел" проблему не прячет.

Гейт несвежих данных

Если данным маркетплейса больше 24 часов, весь пересчёт алертов пропускается. Никаких алертов на устаревших данных: тишина честнее шума.

388 эпизодов в первый день

Первый боевой прогон поднял 388 активных эпизодов. Это накопленный слой проблем, которых раньше просто не было видно. Лента их отсортировала; крайняя срочка встала первой.

Доверие по построению

Заказчик видит тесты каждый день

Крон каждое утро в 05:00 прогоняет все 212 backend-автотестов в чистой отдельной базе. Результат ложится в таблицу и показывается на странице Здоровье, рядом со светофорами контейнеров, памяти, диска и свежести бэкапа. ERP ежедневно доказывает владельцу, что всё ещё работает. Не обещание, а зелёный свет.

Лента алертов с чипами типов, светофорами важности и попапом отзывов
Лента алертов: чипы типов, отметка "видел" на пользователя, крайняя срочка закреплена сверху, негативные отзывы открываются в попапе с пагинацией.
Страница здоровья со светофорами системы и результатами ежедневных автотестов
Страница Здоровье: светофоры контейнеров, памяти, диска и бэкапов, плюс утренний прогон автотестов, видимый заказчику.
7типов алертов, один светофор 33/67
388активных эпизодов в первый день
212+26автотестов с прогоном каждый день в 05:00
5 минобновление статуса хоста на странице Здоровье
06 Инженерия выручки

Комбинаторика наборов: 1 813 путей продажи за 3,0 секунды

Полотенца продаются поштучно и в наборах. Одно и то же сырьё может уйти десятком способов, у каждого своя цена за штуку, маржа и потолок по остаткам. Раньше эту комбинаторику держал в голове заказчик. Теперь её считает система и объясняет каждое предложение в колонке "Почему".

Этап 1: все существующие пути

Для каждого сырья-полотенца: все варианты продажи, поштучно или внутри любого из 349 составов из техкарт МойСклад. По варианту: цена за штуку, маржа, потолок по текущим остаткам, лимитирующий компонент и жадный план распределения. 294 сырья × 349 составов = 1 813 вариантов за 3,0 секунды, сверено вручную SQL-ом на боевых данных.

Этап 2: новые комбинации

Мерчандайзинговое правило заказчика, переведённое в код: розовый сам не продаётся, а в связке с серым улетает. Движок разбирает модельную основу артикула, ставит в пару топ-цвет и неликвид той же основы, скрещивает основы и оценивает экономику каждого кандидата по аналогам.

Колонка "Почему"

У каждого кандидата есть человеческое обоснование: какой цвет тянет, какой остаток разморозит, какая ожидаемая экономика на штуку. Оптимизация сознательно жадная, без LP-солвера: заказчик может проследить каждый шаг, а доверие важнее последнего процента оптимальности.

Комбинаторика наборов: варианты по сырью и кандидаты новых комбинаций с колонкой Почему
Комбинаторика наборов: варианты по сырью на одной вкладке, сгенерированные кандидаты с обоснованием "Почему" на другой, плюс ручной конструктор набора.
1 813вариантов за один прогон
3,0 сполный пересчёт
294 × 349сырьё × составы наборов
100%кандидатов объясняют сами себя
07 Фокус

ABC-анализ: 134 SKU держат 80,2% прибыли

Плавающая ABC-классификация сразу по трём критериям: прибыль, выручка и заказы, с настраиваемыми порогами 80/95 под защитой CHECK-ограничения в базе.

Первый боевой прогон оцифровал интуицию: класс A - это 134 SKU, 17,6% каталога, держащие 80,2% прибыли. Классы сверяются с собственным ABC аналитического сервиса, а убыточные SKU и SKU без данных показываются отдельными корзинами с разложением причин, а не тихо выпадают.

Классы считаются один раз и отдаются по контракту: РНП и досорт-таблица подтягивают букву без пересчётов. ТОП-правило досорта питается именно этими классами.

Зачем это досорту

SKU класса A с покрытием ниже 90% буфера прыгает в верх заказа поставщику, выше любого SKU класса C с худшим процентом. Бизнес защищает те 134 SKU, которые платят за всё остальное. Этот приоритет - оттестированное правило, а не привычка в чьей-то голове.

134SKU в классе A
17,6%каталога...
80,2%...держат такую долю прибыли
3критерия: прибыль, выручка, заказы
08 Замыкание цикла

Заказ поставщику в один клик. Честно: галки плюс одна кнопка.

Буфер говорит, чего не хватает. Модуль заказа говорит, где это взять и к какому сроку, и собирает тот самый Excel, который ждёт производитель в Узбекистане.

  1. Двухпроходное количество. Q(L) = max(0, ceil(V_план × (N + L) × 1.1 - общий остаток)). Проход первый смотрит склад производителя: хватает - режим "со склада", плечо 14 дней. Иначе режим "производство", плечо 74 дня (60 производство + 10 доставка + внутренние плечи), с пометкой, если часть количества есть уже сейчас.
  2. Срочность по третям. Покрытие буфера ниже 1/3: заказывать сейчас. От 1/3 до 2/3: планировать. Выше 2/3: потерпит. Режим производства оценивается на ступень жёстче: длинное плечо оставляет меньше права на ошибку.
  3. Галки, которые выживают. Менеджер отмечает строки в sticky-блоке заказа; галки и ручные количества живут в базе, а не во вкладке браузера. Формирование заказа их не сбрасывает; сбрасывает только явная кнопка.
  4. Одна кнопка, один файл. "Сформировать заказ" собирает Excel в формате, согласованном с поставщиком: артикул производителя из справочника, цвет, режим, количество, срок, затраты на закупку, итоги. Артикул без маппинга получает подсвеченную ячейку "нет в справочнике" вместо тихой пустоты. Файл скачивается и ложится в историю заказов с полным снапшотом позиций.
  5. Грязный вход, чистые данные. Остатки производителя приходят полуструктурированным CSV из системы маркировки. Толерантный regex-парсер отклоняет битые строки поштучно; несматченные артикулы становятся задачами "создать правило" в интерфейсе, и справочник ведёт заказчик, а не разработчик.
14 / 74дней плечо: со склада / с производства
90%пол ТОП-правила для SKU класса A
1клик от отмеченных строк до файла поставщику
100%заказов в истории со снапшотами
09 Сборка FBS

Проверка кода маркировки переехала в момент скана

Склад собирал заказы руками: кабинет площадки, лист подбора, Excel, сканер в Excel, ручная замена скрытых разделителей и загрузка шаблона обратно. Главная потеря времени наступала уже после загрузки: площадка проверяет каждый код в национальной системе прослеживаемости и часть отбраковывает, а люди идут искать эти товары по коробкам. Модуль переносит проверку в момент скана: сборщик узнаёт о плохом коде, пока товар у него в руках.

Экран поставки: задания со статусами и панель сканера с вердиктом по коду маркировки
Страница поставки: слева задания со статусами и подсветкой найденного, справа сканер с крупным фото изделия, стадиями скана и вердиктом по коду. Поле всегда в фокусе: у сборщика в руках сканер и товар, а не мышь.
ВердиктЧто видит сборщик и что делает дальше
Код принятзелёный, задание закрыто, стикер уходит на печать
Принят без проверкижёлтый: реестр не ответил, код принят по настройке, ночью перепроверяется
Код чужойвладелец кода другая компания: отложить, взять другой экземпляр
Уже проданкод выбыл из оборота: отложить, взять другой экземпляр
Не в оборотекод не введён в оборот: отправить в карантин
Заблокированкарантин
Нет в базекарантин
Дублькрасный, с номером задания-владельца: оттуда можно перепечатать стикер
Другой товаркод принадлежит другой карточке: сначала пикнуть штрихкод

Порядок проверок важен. У чужого проданного кода причин отказа несколько, а сборщику нужно назвать одну: ту, с которой он что-то может сделать.

Одна точка входа вместо режимов

Сервер сам классифицирует ввод: штрихкод, код маркировки или стикер, и двигает состояние задания. Переключать режимы человеку с товаром в руках нечем.

Повтор возвращает сохранённый ответ

У каждого нажатия свой идентификатор, и повторный запрос отдаёт тот же ответ, а не пересчитанный. Иначе сборщик увидел бы "этот код уже отсканирован" на собственный же скан.

Импорт не обнуляет работу смены

Повторный импорт обновляет поля площадки и не трогает наши: статус, партию, сканы и коды. Ночной прогон не стирает то, что сделали люди.

Карантин вместо тихой пропажи

Задание с плохим кодом уходит в карантин с причиной, а не исчезает из списка. Начальник склада видит, сколько заданий встало и почему.

Сотрудников заводит начальник склада

Право администратора модуля даёт заводить и выключать сборщиков без администратора портала. Только выключение, не удаление: на человеке висят сканы и сводки.

Сводка по смене

Смена это день по московскому времени. По каждому сотруднику видно: взял с полки, код принят, готово, проблем, отклонённых сканов, первый и последний скан.

Молчаливая деградация здесь недопустима. Если токен доступа к реестру умер, модуль поднимает системный алерт и продолжает работать в режиме "принят без проверки", честно помечая такие коды. Именно тихий отказ и стоил бы той самой отбраковки на площадке, ради которой всё затевалось.

Список поставок FBS с полосой готовности и статусами продавца
Список поставок: зелёная полоса готовности, карантин отдельной колонкой, статусы продавца площадки и назначенные сборщик с клейщиком.
Сводка по смене: кто сколько собрал и сколько проблем
Сводка по смене с итоговой строкой: она отвечает на вопрос начальника склада "кто сегодня сколько собрал", не поднимая никого от стола.
10 Склады

Признак исключения это дата, а не галочка

Справочника складов в системе не было: имя склада жило голой строкой внутри дневных снимков остатков, и выключить склад из расчётов было нечем. Заказчик держал заметную часть товара на складах, которых уже нет, а досорт, товарооборот и РНП считали этот товар живым остатком.

Справочник складов с ролями, кластерами и датой исключения из расчётов
Справочник складов: роль, регион, кластер, остаток и признак участия в расчётах. Наверху доля остатка, лежащая на выключенных складах: до появления справочника этот товар считался живым.

Дата, а не булево

Заказчик просил указывать, с какого числа склад отключён, и пересчитывать историю начиная с неё. Снимки остатков старше этой даты остаются как были: прошлое не переписывается.

Журнал отвечает на вопрос "почему"

Два поля в строке склада отвечают только на вопрос "кто выключил его сейчас". Вопрос заказчика был другой, и на него отвечает история: кто, когда, с какой даты и по какой причине.

Строка пишется по факту изменения

Повторное сохранение с теми же данными журнал не пополняет. Иначе за неделю он зарастает шумом и перестаёт отвечать на исходный вопрос.

Защищённые строки

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

11 Как это построено

Пятнадцать письменных ТЗ, одна пара рук

Сначала бизнес-анализ. Каждый этап начинался с самодостаточного письменного ТЗ: контракты данных, формулы, критерии приёмки. Реализация шла шаг за шагом по железным правилам, записанным после предыдущего ERP-проекта. Каждое изменение небольшим проверяемым шагом.

  1. Согласовано клиентское ТЗ: источники данных, таблица пяти складов, логика буфера, алерты, РНП, комбинаторика наборов. Контракты данных и критерии приёмки зафиксированы до первой строки кода.
  2. Реализованы все функциональные этапы ТЗ-00...ТЗ-10: усиленная инфраструктура, портал с ролями, три API-синка, РНП, Google-витрина, контроль товарооборота с DBM, алерты, дашборд, наборы, экспорт, ABC.
  3. Редизайн интерфейса: тёмный сворачиваемый сайдбар, карточный режим, 10 тем на CSS-токенах. Контраст всех тем проверяется тестом, а не глазами.
  4. Приёмка - это документ. Отчёт приёмки на 630 строк, матрица покрытия требований на 187 строк и честный раздел с 18 недочётами и отступлениями. Каждая цифра этого кейса прослеживается до отчёта или репозитория.
Честные ограничения

Чего система не делает вид, что знает

Автоматический сезонный коэффициент пока равен 1: API маркетплейса хранит только ~6 скользящих месяцев истории, поэтому сезонность год к году физически недоступна. Система честно пишет "мало истории" и копит собственную. Ключ сервисного аккаунта Google на приёмке ещё не был передан, поэтому модуль мягко пропускает работу, а не падает. Парсинг конкурентов написан, но ждёт прокси. Все три пункта записаны в отчёте приёмки, а не обнаружены потом.

Стек

PythonFastAPISQLAlchemy 2 asyncAlembic Celery + BeatPostgreSQL 16Redis 7 Docker Composenginx React 18TypeScriptViteTailwind Radix UIZustandTanStack Queryrecharts

Интеграции

Wildberries APIAPI МойСклад API TrueStatsGoogle Sheets API

Темп поставки

62 таблицы БД12 письменных ТЗ 33K строк кода212+26 автотестов
12 Итог

Было и стало

ОбластьБылоСтало
Решения по досорту Личный Excel владельца, ежедневно, по ощущениям. Держалось на одном человеке. Буферы ТОС с зонами-светофором по каждому SKU, самокоррекция шагами ±1/3, каждое изменение подтверждает человек.
Данные Три сервиса плюс ручные выгрузки. Единой картины нет. Один PostgreSQL, ночной конвейер с гейтами свежести и идемпотентными импортами.
Обнаружение проблем Глазами, когда кто-то случайно посмотрел. 7 типов алертов; 388 ранее невидимых эпизодов подняты в первый же день, крайняя срочка всегда сверху.
Экономика месяца Понятна после окончания месяца. Дашборд с прогнозом конца месяца по тренду 15 дней, за 61 мс.
Планирование наборов Комбинаторика в голове владельца. 1 813 рассчитанных вариантов за 3,0 с, у каждого нового кандидата письменное "Почему".
Заказ поставщику Собирался вручную под каждый заказ. Галки плюс одна кнопка: точный Excel-формат поставщика, с историей и снапшотами.
Доверие "Должно работать." 212+26 автотестов каждое утро в чистой базе, результат виден заказчику на странице Здоровье.

Кирилл Седов

Бизнес-аналитик и строитель систем. Начинаю с бизнес-процесса и контракта данных, потом автоматизирую. Эта ERP спроектирована, описана и выведена в прод и ведёт досорт заказчика на буферах Голдратта, а не на интуиции.

Связаться в LinkedIn