01 · Контекст и задача
Клиент — оптовый дистрибьютор автомобильных масел, смазок и фильтров. Каждый день со склада уходят заказы, которые надо развезти по адресам. Машин несколько (Газель, Камаз, Лада Вис), у каждой свой кузов и грузоподъёмность. Задача упаковщика — разложить заказы так, чтобы всё влезло и машины не гоняли полупустыми.
Раньше это делалось на глаз и в Excel: ошибки, перевесы, лишние рейсы. Задача: система, которая принимает заказы из 1С и сама считает оптимальную раскладку по машинам — с учётом веса, объёма, габаритов каждого товара, типов коробок и правил упаковки.
02 · Что было сложно
- Это задача оптимизации, а не CRUD. «Разложить N товаров по M машинам так, чтобы влезло и оптимально» — это классическая NP-трудная задача упаковки (bin packing). Простым перебором не решить: на десятках позиций комбинаций астрономически много. Нужен настоящий решатель, а не «если-то».
- Геометрия реальных товаров. Канистра масла, цилиндрическая банка смазки, фильтр в заводской коробке — у каждого свой размер и форма. Что-то едет в фирменной коробке, что-то раскладывается по гофрокоробам со склада, что-то — без упаковки. Все эти правила надо было заложить в расчёт.
- Расчёт должен быть детерминированным. Если на одних и тех же данных система выдаёт каждый раз разную раскладку — упаковщик ей не поверит. Один и тот же заказ должен раскладываться одинаково, и по возможности — в одну машину.
- 1С как источник данных. Заказы и товары приходят из 1С, а у 1С нет REST API — обмен идёт XML-файлами по протоколу CommerceML. Нужно было сделать приёмник, который корректно разбирает выгрузку, отличает заказы от служебных файлов и не падает на кривых данных.
Простых админок на рынке тысячи. А систему, которая реально считает оптимальную загрузку транспорта, — единицы. Это инженерная задача, а не вёрстка форм.
03 · Решения
Отдельный сервис-оптимизатор на OR-Tools
Расчёт раскладки вынесли в отдельный микросервис на FastAPI + Google OR-Tools — это промышленный решатель задач комбинаторной оптимизации (тот же, что Google использует для своих логистических задач). NestJS-бэкенд готовит данные, передаёт оптимизатору, получает оптимальную раскладку и сохраняет. Решатель учитывает вес, объём, габариты и правила упаковки одновременно.
Ручная корректировка поверх расчёта
Автоматика даёт старт, но последнее слово — за человеком. Менеджер может перетащить позицию из одной машины в другую мышью, и система пересчитает загрузку. После правок «Сохранить ручные правки» фиксирует итоговую раскладку. Машинный расчёт и человеческий контроль работают вместе.
Справочники с реальной геометрией
Товары хранятся с формой (прямоугольник Д×Ш×В или цилиндр), весом и правилом упаковки. Коробки — свой справочник с размерами. Машины — с габаритами кузова и грузоподъёмностью. Часть товаров классифицируется автоматически по литражу. Всё это — данные, на которых работает оптимизатор.


Приёмник 1С по CommerceML
Сделали endpoint, который принимает выгрузку 1С по протоколу CommerceML напрямую (без архивов). Каждая загрузка фиксируется в истории импортов: что пришло, сколько заказов, статус (успех/ошибка), новые и обновлённые. Служебные файлы 1С отсекаются и помечаются отдельно — менеджер видит только реальные заказы.
Роли и аудит
Два уровня доступа: администратор (полный) и менеджер (только заказы, распределение, импорты). Каждое значимое действие пишется в журнал аудита — кто, что и когда. Для склада, где несколько человек работают с заказами, это базовая необходимость.
04 · Результат
Этап 1 сдан и работает в проде. Менеджер загружает заказы из 1С, нажимает «Рассчитать» — и за секунды получает раскладку по машинам с процентом загрузки каждой. Видно, что влезло, что нет, и в каких коробках едет каждая позиция. Раскладку можно поправить мышью и сохранить.
Расчёт детерминированный: одни и те же заказы дают одну и ту же раскладку, система старается уложить заказ в одну машину. Бизнес получил инструмент, который убирает перевесы и лишние рейсы — то, что раньше считалось на глаз.
05 · Что дальше
Проект развивается этапами. В работе — поддержка паллет (европаллет, американский, китайский) с заданием вместимости машин в паллетах, а не только по весу и объёму. Это следующий слой оптимизации поверх уже работающего ядра.
Архитектура к этому готова: оптимизатор вынесен в отдельный сервис, его логику можно расширять, не трогая бэкенд и интерфейс. Добавление нового типа упаковки или правила — это данные и параметры решателя, а не переписывание системы.