Полный бэк-офис для селлера домашнего текстиля на Wildberries: 763 карточки, 4 бренда, около 6 000 заказов в день, производство в Узбекистане с циклом ~60 дней. Система охватывает: досорт на буферах ТОС, ежедневная таблица РНП, 7 типов алертов, дашборд экономики с прогнозом месяца, комбинаторика наборов, ABC-анализ, заказ поставщику в один клик, а с этого года ещё сборку заказов FBS со сканером кодов маркировки и справочник складов.
Быстрорастущий селлер полотенец, халатов и текстильных наборов. Цифры жили в трёх несвязанных сервисах. Главное решение бизнеса, что и когда досортировывать, принималось вручную.
Кабинет маркетплейса: заказы и реклама. МойСклад: склады и составы наборов. TrueStats: юнит-экономика. Плюс ручные выгрузки файлами. Единой картины не видел никто.
Какой SKU везти на склад маркетплейса, сколько и когда: владелец решал это ежедневно, в личном Excel, по 763 карточкам в ~27 категориях. Процесс не масштабировался и не переживал отпуск.
Производство занимает около 60 дней, плюс ~10 дней доставка, плюс внутренние плечи. Заказал поздно - пропустил сезон. Заказал рано - заморозил деньги в остатках. Права на ошибку почти нет, а в сезон его нет совсем.
Обнуление остатков, падение заказов, негативные отзывы и расхождения данных всплывали, когда кто-то случайно посмотрел. При 6 000 заказов в день "случайно посмотрел" - это не система контроля.
Каждую ночь Celery-задачи стягивают маркетплейс, МойСклад и TrueStats в PostgreSQL. Расчётные модули превращают сырые строки в буферы, алерты и прогнозы. Люди работают в React-портале или в Google-таблице, которую бэкенд рендерит для менеджеров.
Контракты между модулями означают, что ничего не считается дважды: достаточность даёт одна функция, светофор 33/67 живёт в одной настройке, классы ABC отдаёт один сервис. РНП, Google-витрина и алерты потребляют одни и те же цифры.
Заказчик попросил проверить его формулы досорта и предложить улучшения. Его классический расчёт покрытия остался в таблице видимыми колонками. Поверх система ведёт динамическое буферное управление (DBM): у каждого SKU есть целевой буфер, буфер делится на зоны-светофор, и буфер сам корректируется по фактическому потреблению. Это ТОС по учебнику, реализованная кодом и тестами.
×1.1 - это запас "на паранойю" от заказчика на каждый буфер, в духе Голдратта. M - множитель DBM: стартует с 1.00 и меняется шагами по 1/3.
Буфер заполнен достаточно. Ничего не делать. Двенадцать зелёных дней подряд предложат уменьшить буфер: капитал не должен спать в остатках.
Планировать пополнение. SKU попадает в досорт-таблицу с рассчитанным количеством, а не с догадкой.
Заказывать сейчас. Красные дни копятся в 12-дневной истории; красная серия запускает рекомендацию увеличить буфер.
Остаток ноль при живых продажах. Потерянная выручка каждый день. Чёрная считается красной в сериях и кричит с верха ленты алертов.
| Константа | Значение | Обоснование |
|---|---|---|
| Окно DBM | 12 дней | Три плеча пополнения склада маркетплейса (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 с подсказкой, почему. Менеджер нажимает, изменение логируется, стартует кулдаун. Это дисциплина самого Голдратта: буферы адаптируются, люди утверждают.
РНП, "Рука на Пульсе", - ежедневная панель по каждому SKU: воронка продаж, экономика рекламы, остатки и цены, около 60 колонок на SKU, с виртуализацией, чтобы 763 строки листались как шёлк. Дашборд отвечает на вопрос владельца: чем закончится месяц, если едем как едем?
763 строки × ~60 колонок со sticky-колонкой SKU и настраиваемыми светофорами. Карточный режим для работы по исключениям. Каждый SKU раскрывается в карточку: продажи, реклама, остатки, цены и комментарии в окнах 7 / 14 / 30 дней.
Выгрузка воронки маркетплейса прилетает drag-and-drop. Колонки матчатся нечётким маппингом; переименованная колонка вызывает диалог подтверждения, а не тихое неверное чтение. Битые строки отклоняются поштучно, файл целиком не падает.
Выручка TrueStats и маркетплейса сравнивается ежедневно. Расхождение больше 5% поднимает баннер. В проде это поймало реальные 13,31% на первом же боевом прогоне.
Экономика кабинета и категорий из файла, API или их слияния по SKU. Прогноз конца месяца по тренду 15 дней. Расходные метрики инвертируют окраску: растущая реклама красная, а не зелёная. Ответ: 61 мс с кэшем, 101 мс без.
Бэкенд рендерит витринные листы прямо в привычную таблицу: главная, сезон, распределение,
новые цены, контроль остатков, алерты. Запись только RAW, чтобы штрихкоды не превращались
в 2.05E+12. Ручные правки возвращаются дифф-пуллом каждые 10 минут, и рендер
никогда не затирает ячейку человека. Кнопка в таблице отправляет новые цены назад через
вебхук с проверкой токена.
Проблемы должны сами находить людей, а не ждать, пока их найдут. Семь детерминированных типов алертов следят за бизнесом; страница здоровья со светофорами следит за самой системой.
| Алерт | Триггер | Детали |
|---|---|---|
| Крайняя срочка | ≤100 штук на маркетплейсе | Всегда красный, всегда закреплён вверху ленты. |
| Низкая достаточность | покрытие ниже порога | Тот же контракт достаточности, что и в досорт-таблице. Одна формула, одна правда. |
| Падение заказов | скорость за 3 дня против 10 дней, ≥30% вниз | Ловит умирающий SKU за дни, а не за недели. |
| Всплеск заказов | скорость за 3 дня резко вверх | Всплеск - тоже сигнал досорта: продавать, пока летит. |
| Негативные отзывы | новые отзывы на 1-3 звезды | Попап с текстами отзывов, серверная пагинация. На приёмке в базе было 1 447 негативных. |
| Старт производства | <90 дней запаса | Отсчитывает 60-дневный цикл производства назад от прогнозного обнуления, вне сезона смягчается. |
| Расхождение данных | разрыв TrueStats и WB >5% | Система не доверяет своим входам раньше, чем предложит доверять своим выводам. |
Алерт - это эпизод: активен, решён, реактивирован в тот же день, если условие вернулось. Ноль дублей по построению. Отметка "видел" - на каждого пользователя, чужое "видел" проблему не прячет.
Если данным маркетплейса больше 24 часов, весь пересчёт алертов пропускается. Никаких алертов на устаревших данных: тишина честнее шума.
Первый боевой прогон поднял 388 активных эпизодов. Это накопленный слой проблем, которых раньше просто не было видно. Лента их отсортировала; крайняя срочка встала первой.
Крон каждое утро в 05:00 прогоняет все 212 backend-автотестов в чистой отдельной базе. Результат ложится в таблицу и показывается на странице Здоровье, рядом со светофорами контейнеров, памяти, диска и свежести бэкапа. ERP ежедневно доказывает владельцу, что всё ещё работает. Не обещание, а зелёный свет.
Полотенца продаются поштучно и в наборах. Одно и то же сырьё может уйти десятком способов, у каждого своя цена за штуку, маржа и потолок по остаткам. Раньше эту комбинаторику держал в голове заказчик. Теперь её считает система и объясняет каждое предложение в колонке "Почему".
Для каждого сырья-полотенца: все варианты продажи, поштучно или внутри любого из 349 составов из техкарт МойСклад. По варианту: цена за штуку, маржа, потолок по текущим остаткам, лимитирующий компонент и жадный план распределения. 294 сырья × 349 составов = 1 813 вариантов за 3,0 секунды, сверено вручную SQL-ом на боевых данных.
Мерчандайзинговое правило заказчика, переведённое в код: розовый сам не продаётся, а в связке с серым улетает. Движок разбирает модельную основу артикула, ставит в пару топ-цвет и неликвид той же основы, скрещивает основы и оценивает экономику каждого кандидата по аналогам.
У каждого кандидата есть человеческое обоснование: какой цвет тянет, какой остаток разморозит, какая ожидаемая экономика на штуку. Оптимизация сознательно жадная, без LP-солвера: заказчик может проследить каждый шаг, а доверие важнее последнего процента оптимальности.
Плавающая ABC-классификация сразу по трём критериям: прибыль, выручка и заказы, с настраиваемыми порогами 80/95 под защитой CHECK-ограничения в базе.
Первый боевой прогон оцифровал интуицию: класс A - это 134 SKU, 17,6% каталога, держащие 80,2% прибыли. Классы сверяются с собственным ABC аналитического сервиса, а убыточные SKU и SKU без данных показываются отдельными корзинами с разложением причин, а не тихо выпадают.
Классы считаются один раз и отдаются по контракту: РНП и досорт-таблица подтягивают букву без пересчётов. ТОП-правило досорта питается именно этими классами.
SKU класса A с покрытием ниже 90% буфера прыгает в верх заказа поставщику, выше любого SKU класса C с худшим процентом. Бизнес защищает те 134 SKU, которые платят за всё остальное. Этот приоритет - оттестированное правило, а не привычка в чьей-то голове.
Буфер говорит, чего не хватает. Модуль заказа говорит, где это взять и к какому сроку, и собирает тот самый Excel, который ждёт производитель в Узбекистане.
Q(L) = max(0, ceil(V_план × (N + L) × 1.1 - общий остаток)). Проход первый смотрит склад производителя: хватает - режим "со склада", плечо 14 дней. Иначе режим "производство", плечо 74 дня (60 производство + 10 доставка + внутренние плечи), с пометкой, если часть количества есть уже сейчас.Склад собирал заказы руками: кабинет площадки, лист подбора, Excel, сканер в Excel, ручная замена скрытых разделителей и загрузка шаблона обратно. Главная потеря времени наступала уже после загрузки: площадка проверяет каждый код в национальной системе прослеживаемости и часть отбраковывает, а люди идут искать эти товары по коробкам. Модуль переносит проверку в момент скана: сборщик узнаёт о плохом коде, пока товар у него в руках.
| Вердикт | Что видит сборщик и что делает дальше |
|---|---|
| Код принят | зелёный, задание закрыто, стикер уходит на печать |
| Принят без проверки | жёлтый: реестр не ответил, код принят по настройке, ночью перепроверяется |
| Код чужой | владелец кода другая компания: отложить, взять другой экземпляр |
| Уже продан | код выбыл из оборота: отложить, взять другой экземпляр |
| Не в обороте | код не введён в оборот: отправить в карантин |
| Заблокирован | карантин |
| Нет в базе | карантин |
| Дубль | красный, с номером задания-владельца: оттуда можно перепечатать стикер |
| Другой товар | код принадлежит другой карточке: сначала пикнуть штрихкод |
Порядок проверок важен. У чужого проданного кода причин отказа несколько, а сборщику нужно назвать одну: ту, с которой он что-то может сделать.
Сервер сам классифицирует ввод: штрихкод, код маркировки или стикер, и двигает состояние задания. Переключать режимы человеку с товаром в руках нечем.
У каждого нажатия свой идентификатор, и повторный запрос отдаёт тот же ответ, а не пересчитанный. Иначе сборщик увидел бы "этот код уже отсканирован" на собственный же скан.
Повторный импорт обновляет поля площадки и не трогает наши: статус, партию, сканы и коды. Ночной прогон не стирает то, что сделали люди.
Задание с плохим кодом уходит в карантин с причиной, а не исчезает из списка. Начальник склада видит, сколько заданий встало и почему.
Право администратора модуля даёт заводить и выключать сборщиков без администратора портала. Только выключение, не удаление: на человеке висят сканы и сводки.
Смена это день по московскому времени. По каждому сотруднику видно: взял с полки, код принят, готово, проблем, отклонённых сканов, первый и последний скан.
Молчаливая деградация здесь недопустима. Если токен доступа к реестру умер, модуль поднимает системный алерт и продолжает работать в режиме "принят без проверки", честно помечая такие коды. Именно тихий отказ и стоил бы той самой отбраковки на площадке, ради которой всё затевалось.
Справочника складов в системе не было: имя склада жило голой строкой внутри дневных снимков остатков, и выключить склад из расчётов было нечем. Заказчик держал заметную часть товара на складах, которых уже нет, а досорт, товарооборот и РНП считали этот товар живым остатком.
Заказчик просил указывать, с какого числа склад отключён, и пересчитывать историю начиная с неё. Снимки остатков старше этой даты остаются как были: прошлое не переписывается.
Два поля в строке склада отвечают только на вопрос "кто выключил его сейчас". Вопрос заказчика был другой, и на него отвечает история: кто, когда, с какой даты и по какой причине.
Повторное сохранение с теми же данными журнал не пополняет. Иначе за неделю он зарастает шумом и перестаёт отвечать на исходный вопрос.
Псевдосклады "в пути" и точки приёмки исключать нельзя: они не хранят товар, а обозначают его движение. Попытка выключить такую строку отвергается сервером, а не только гардом на фронте.
Сначала бизнес-анализ. Каждый этап начинался с самодостаточного письменного ТЗ: контракты данных, формулы, критерии приёмки. Реализация шла шаг за шагом по железным правилам, записанным после предыдущего ERP-проекта. Каждое изменение небольшим проверяемым шагом.
Автоматический сезонный коэффициент пока равен 1: API маркетплейса хранит только ~6 скользящих месяцев истории, поэтому сезонность год к году физически недоступна. Система честно пишет "мало истории" и копит собственную. Ключ сервисного аккаунта Google на приёмке ещё не был передан, поэтому модуль мягко пропускает работу, а не падает. Парсинг конкурентов написан, но ждёт прокси. Все три пункта записаны в отчёте приёмки, а не обнаружены потом.
| Область | Было | Стало |
|---|---|---|
| Решения по досорту | Личный Excel владельца, ежедневно, по ощущениям. Держалось на одном человеке. | Буферы ТОС с зонами-светофором по каждому SKU, самокоррекция шагами ±1/3, каждое изменение подтверждает человек. |
| Данные | Три сервиса плюс ручные выгрузки. Единой картины нет. | Один PostgreSQL, ночной конвейер с гейтами свежести и идемпотентными импортами. |
| Обнаружение проблем | Глазами, когда кто-то случайно посмотрел. | 7 типов алертов; 388 ранее невидимых эпизодов подняты в первый же день, крайняя срочка всегда сверху. |
| Экономика месяца | Понятна после окончания месяца. | Дашборд с прогнозом конца месяца по тренду 15 дней, за 61 мс. |
| Планирование наборов | Комбинаторика в голове владельца. | 1 813 рассчитанных вариантов за 3,0 с, у каждого нового кандидата письменное "Почему". |
| Заказ поставщику | Собирался вручную под каждый заказ. | Галки плюс одна кнопка: точный Excel-формат поставщика, с историей и снапшотами. |
| Доверие | "Должно работать." | 212+26 автотестов каждое утро в чистой базе, результат виден заказчику на странице Здоровье. |
Бизнес-аналитик и строитель систем. Начинаю с бизнес-процесса и контракта данных, потом автоматизирую. Эта ERP спроектирована, описана и выведена в прод и ведёт досорт заказчика на буферах Голдратта, а не на интуиции.