Тимур ТурдыевНаписать

кейс · 2026

Учёт производства с нуля: платы, сканер и один маршрут в коде

Написал систему учёта плат для производства Waviot: сначала прототип на готовой админке, потом своя платформа. С июня 2026 года цех ведёт в ней каждую плату, и теперь в неё переезжает всё производство с легаси 2016 года.

Laravel 12PHP 8.5InertiaReactTypeScriptMySQLPest

Что было ​

Через производство Waviot прошли миллионы приборов, сейчас это десятки тысяч в месяц, и с системами там работают больше ста человек. Всё это держится на легаси на Python, которое пишется с 2016 года. С 2024-го я единственный, кто его развивает, и оно честно делает своё: приборы, коробки, отгрузки, паспорта, метрология. Но у неё нет понятия "плата". Плата появляется в учёте только в момент, когда её прошили и привязали к модему. Всё, что происходит с ней до этого, монтаж, визуальный контроль, брак, передача между отделами, жило на бумаге и в головах начальников смен.

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

Прототип, который я выбросил ​

В декабре 2025 я за две недели собрал прототип на MoonShine: модели отделов, моделей и номеров плат, экран сканирования, выгрузка в Excel, роли в простом виде. Это был осознанный MVP: проверить, понимают ли люди в цеху саму идею "сканируешь, и система решает, что это значит".

Идею приняли. А прототип я закрыл. Уже на второй неделе стало видно, куда это идёт: форму входа пришлось копировать из пакета в свой код, чтобы менять, а главный экран цеха, сканирование, жил своим JS поверх админки, а не внутри её CRUD-модели. Готовая админка хороша, когда продукт и есть админка. Здесь продуктом был рабочий процесс цеха, и его надо было писать своим.

В январе 2026 я начал заново: чистый Laravel 12, свой набор компонентов, свой табличный движок. Через четыре месяца первый merge в master, в июне первое сканирование на бою.

Как устроено ​

Оператор делает одно действие: сканирует. Что значит скан, решает сервер по маршруту отдела: принять плату, передать дальше, положить в коробку, отказать с понятной причиной. Исходов семь, и каждый показывается словами, а не кодом ошибки. Вход на планшете по QR-бейджу, киоск-режим, долгие сессии сканера: человек у стола не должен вводить пароль в перчатках.

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

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

Номера плат под гравировку выдаются непрерывным диапазоном. Заказ резервирует пул id: строки вставляются с явными id, первой идёт якорная строка в конце диапазона, чтобы параллельная вставка в MySQL 8 не попала в середину пула. Плата из резерва при скане отвечает "не выдана", а не "не найдена".

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

Цех не офис. Сканер узнаётся по скорости ввода, разорванный переводами строк QR склеивается по паузе, ввод в русской раскладке переводится по положению клавиш. Крупные цели нажатия на сенсоре, переключаемый размер шрифта, звук на ошибке. После выхода кнопка "Назад" на общем планшете не показывает чужие данные.

Фронт переезжает по одной странице. Старые экраны на Blade и Bootstrap, новые с сентября на Inertia и React с TypeScript. Типы для страниц не пишутся руками: контроллер отдаёт DTO, команда генерирует .ts, проверка в CI ловит устаревшие. Правила (доступные переходы, причины отказа, цвета сроков) считает сервер, фронт только показывает. Архитектурный тест запрещает домену знать про HTTP и фасады Laravel.

Что вышло ​

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

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

Сложная логика закрыта тестами на Pest, фронт на Vitest, ключевые сценарии сквозными тестами на Playwright.

Коллега весной помог с ролями, сидерами и тестами прав, другой коллега настроил CI стенда. Остальное моё: архитектура, все крупные модули, фронт.

Вывод ​

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

© 2014-2026 Тимур ТурдыевСайт и справочник работают на VPS от AdminVPS