Маркировка перестала быть опцией. С 2024 года под неё попадают всё новые категории товаров, а с 2026 года — практически весь розничный оборот. Для бизнеса это означает не просто покупку принтера этикеток. Это означает, что в ERP должна появиться полноценная статусная машина кодов маркировки, которая отслеживает каждый КМ от момента заказа у оператора до момента выбытия из оборота. Без такой машины склад начинает продавать товар, которого в ГИС МТ ещё нет, а бухгалтерия получает штрафы за расхождения.
Зачем нужна статусная машина в интеграции с Честным ЗНАКом
Без статусной машины код маркировки — это просто поле в таблице. Вы не знаете, заказан ли он у оператора, напечатана ли этикетка, агрегирован ли в короб, передан ли в магазин, продан ли покупателю или списан. Каждый из этих переходов требует обращения к ГИС МТ, и каждое обращение может завершиться ошибкой, задержкой или таймаутом.
Статусная машина решает три задачи. Первая — она не позволяет совершить операцию, для которой код ещё не готов. Нельзя агрегировать коды в короб, если они ещё в статусе «эмиссия заказана, но не подтверждена». Нельзя продать товар, если код не прошёл этап ввода в оборот. Вторая задача — она позволяет отследить, на каком этапе застрял конкретный код, когда произошло это и почему. Третья — она даёт точку входа для retry: если переход в статус не удался, система знает, что нужно повторить, а не начинать процесс сначала.
Жизненный цикл кода маркировки: от эмиссии до выбытия
Мы выделили восемь ключевых статусов, через которые проходит типовой код маркировки в розничном бизнесе. Не все статусы обязательны для каждой категории товаров, но архитектура должна быть готова к любому из них.
| Статус | Что происходит | Интеграция с ГИС МТ |
|---|---|---|
draft |
Код ещё не заказан, но уже привязан к товарной позиции в 1С | Нет |
ordered |
Заявка на эмиссию отправлена в ГИС МТ | POST /api/v3/true-api/cises/create |
emitted |
ГИС МТ подтвердил эмиссию, коды получены | GET /api/v3/true-api/cises/list |
printed |
Этикетка напечатана и наклеена на товар | Нет (локальное действие) |
introduced |
Код введён в оборот (производство или импорт) | POST /api/v3/true-api/lk/documents/creation |
aggregated |
Коды упакованы в агрегат (короб, паллету) | POST /api/v3/true-api/documents/aggregation/create |
shipped |
Товар отгружен контрагенту, код передан в ГИС МТ | POST /api/v3/true-api/lk/documents/creation (УПД) |
retired |
Код выбыт из оборота (продажа, списание, возврат) | POST /api/v3/true-api/documents/retirement |
Важно понимать: статусы не всегда идут строго последовательно. Для импортного товара эмиссия и ввод в оборот могут происходить одним пакетом. Для розничного товара агрегация может отсутствовать. Для списания код может перейти из introduced сразу в retired, минуя shipped. Статусная машина должна быть готова к разрывам цепочки и не падать, если какой-то этап пропущен по бизнес-причинам.
Архитектура коннектора: три слоя
Мы построили коннектор 1С и Честный ЗНАК как отдельный сервис на Symfony, аналогично коннекторам с маркетплейсами и СБИС. Интеграция вынесена из 1С по тем же причинам: встроенный язык не даёт retry, очередей и тестируемости, а нагрузка на базу 1С от обмена с ГИС МТ может быть значительной.
Слой 1. Локальное хранение кодов и их статусов
Каждый код маркировки хранится в таблице marking_code со следующими полями: сам КМ (строка), GTIN, серийный номер, текущий статус, предыдущий статус, дата последнего изменения, correlation ID последнего запроса к ГИС МТ, количество попыток retry, идентификатор документа в ГИС МТ. Эта таблица — единственный источник правды о состоянии кодов внутри нашей системы. 1С читает из неё, а не напрямую из ГИС МТ.
Почему не хранить статус в 1С? Потому что 1С — это учётная система, а не интеграционная. Статус КМ — это техническая сущность, которая меняется чаще, чем бухгалтерские проводки, и требует истории изменений, которую нецелесообразно вести в учётной базе. Отдельная таблица в коннекторе позволяет вести аудит, строить отчёты и делать массовые операции без блокировки 1С.
Слой 2. Статусная машина (State Machine)
Для управления переходами мы используем Symfony Workflow. Каждый статус — это место (place), каждый допустимый переход — это transition. Workflow не позволяет перевести код из draft сразу в introduced, потому что такого transition нет. Это защита от ошибок бизнес-логики на уровне фреймворка, а не на уровне if-else в коде.
Переходы могут быть синхронными и асинхронными. Синхронные — это локальные операции: printed наступает, когда оператор на складе сканирует код и подтверждает печать. Асинхронные — это операции, требующие ГИС МТ: ordered → emitted требует ожидания ответа от оператора. Асинхронные переходы не выполняются напрямую, а порождают сообщение в очередь Symfony Messenger.
Слой 3. Клиент ГИС МТ с retry и circuit breaker
API Честного Знака известно своей нестабильностью. Таймауты, 500-е ошибки, перегрузки в пиковые часы — это норма, а не исключение. Клиент должен быть готов к этому лучше, чем разработчик, который его писал. Мы реализовали три механизма защиты.
Retry с экспоненциальным backoff. При 5xx или таймауте сообщение возвращается в очередь с задержкой: 1 минута, 5 минут, 15 минут, 1 час. После четырёх попыток сообщение уходит в dead-letter очередь для ручного разбора. При 4xx (невалидный КМ, отсутствие прав) retry не делается — это бизнес-ошибка, которую нужно исправлять в данных, а не повторять запрос.
Circuit breaker. Если ГИС МТ возвращает ошибки подряд более 10 раз за 5 минут — клиент переключается в режим «открытой цепи» и перестаёт делать запросы на 10 минут. Вместо этого новые сообщения накапливаются в очереди. Это защищает и наш сервис от бесконечных таймаутов, и ГИС МТ от лишней нагрузки в момент аварии.
Идемпотентность. Каждый запрос к ГИС МТ снабжается уникальным correlation ID. Если по таймауту мы не знаем, прошёл ли запрос — мы можем повторить его с тем же ID. ГИС МТ отбрасывает дубли по correlation ID, и код не переходит в статус дважды. Это критично для операций ввода в оборот и агрегации, где повторная отправка может создать неконсистентность.
Как статусная машина решает реальные проблемы
Теория хороша, но статусная машина окупается только когда решает конкретные инциденты. Вот три сценария, которые мы отловили в продакшене.
Сценарий 1. Эмиссия подвисла на 48 часов
Коды были заказаны, но ГИС МТ не вернул их в течение двух суток. Без статусной машины склад мог бы начать печать и ввод в оборот, полагаясь на то, что «всё уже должно быть». Со статусной машиной коды остались в ordered, система алертнула, что эмиссия не подтверждена более 24 часов, и склад получил блокировку на отгрузку этой номенклатуры до разрешения инцидента.
Сценарий 2. Частичная агрегация
Оператор собрал короб из 100 единиц, но 3 кода не прошли агрегацию в ГИС МТ из-за ошибки валидации. Без статусной машины весь короб мог бы остаться в статусе «не агрегирован», и при отгрузке контрагент получил бы 97 кодов в коробе и 3 «висячих». Со статусной машиной система зафиксировала: 97 кодов — aggregated, 3 кода — introduced с ошибкой. Короб был отгружен как агрегат, а 3 кода обработаны отдельно после исправления данных.
Сценарий 3. Продажа до ввода в оборот
Касса получила код от склада и попыталась продать товар. Но код ещё не прошёл этап introduced — ввод в оборот застрял в очереди из-за сбоя ГИС МТ. Без статусной машины касса бы пробила чек, а ГИС МТ отклонил бы выбытие. Со статусной машиной касса получила отказ: «Код не готов к продаже», чек не пробился, покупатель был переадресован на другую позицию, а код дождался ввода в оборот и был продан позже.
Синхронизация с 1С: OData и события
Коннектор не заменяет 1С. Он дополняет её. Связь построена через два канала: OData для чтения и вебхуки/очередь для записи.
1С сообщает коннектору о событиях, требующих действий с ГИС МТ: создание заказа на производство, приёмка импортного товара, отгрузка контрагенту, продажа в розницу. Это происходит через запись в регистр сведений, который коннектор опрашивает по OData. Коннектор обрабатывает событие, меняет статус КМ, и при необходимости пишет результат обратно в 1С — например, статус агрегации или номер документа в ГИС МТ.
Важное правило: 1С — мастер для бизнес-данных, коннектор — мастер для технических статусов КМ. 1С не знает, в каком статусе конкретный код в ГИС МТ. Коннектор не знает, по какой цене и кому продан товар. Граница ответственности чёткая, и это позволяет обновлять каждую систему независимо.
Мониторинг: как не пропустить застрявший код
Статусная машина бесполезна, если вы не видите, сколько кодов в каждом статусе и как долго они там находятся. Мы настроили три уровня наблюдаемости.
Prometheus + Grafana показывает количество кодов в каждом статусе в реальном времени. Аномалия — рост кодов в ordered более 24 часов или скопление в emitted без перехода в printed.
Если код не меняет статус дольше допустимого SLA — приходит алерт. Для ordered → emitted SLA — 4 часа. Для introduced → shipped — 24 часа. Для shipped → retired — 72 часа.
Коды, которые исчерпали retry, попадают в отдельную таблицу с описанием ошибки. Оператор разбирает их вручную, исправляет данные и переводит обратно в поток. Это не авария — это нормальный процесс, который ловит краевые случаи.
Кому нужен такой коннектор
Не каждому бизнесу требуется полноценная статусная машина. Если вы продаёте 50 позиций в месяц и печатаете коды вручную — достаточно веб-интерфейса Честного Знака. Но если вы работаете с тысячами кодов, имеете складскую логистику с агрегацией и отгружаете контрагентам — без автоматизации не обойтись.
| Критерий | Ручной режим | Коннектор со статусной машиной |
|---|---|---|
| Объём кодов в месяц | До 500 | От 1 000 и выше |
| Агрегация в короба/паллеты | Редко | Обязательно |
| Сколько систем используют код | 1С + ГИС МТ | 1С + WMS + касса + ГИС МТ |
| Требование к скорости | «В течение дня» | «До отгрузки» или «До продажи» |
| Штрафы за ошибки | Минимальны | Значительны, требуют контроля |
Тестирование статусной машины: как проверить переходы, которые зависят от внешнего API
Статусная машина, которая опирается на ГИС МТ, сложно тестируется. Вы не можете в unit-тесте отправить реальный запрос в ГИС МТ, а мок, который всегда возвращает 200, не проверяет логику retry и обработки ошибок. Мы выстроили три уровня тестирования, которые дают уверенность в том, что статусная машина работает правильно.
Unit-тесты переходов Workflow
Symfony Workflow позволяет тестировать переходы независимо от внешних сервисов. Мы создаём объект КМ, задаём ему начальный статус, вызываем workflow->apply() с нужным transition, и проверяем, что статус изменился корректно. Это быстро, не требует базы данных, и покрывает бизнес-логику: можно ли перевести из emitted в introduced, что произойдёт при попытке недопустимого перехода, какие события срабатывают при смене статуса.
Интеграционные тесты с моками HTTP
Для тестирования клиента ГИС МТ мы используем Symfony HttpClientMock. Он перехватывает HTTP-запросы и возвращает заранее заготовленные ответы. У нас есть набор «золотых записей»: успешная эмиссия, таймаут, 500-я ошибка, невалидный КМ, дубль correlation ID. Каждый сценарий — отдельный тест, который проверяет, что статусная машина реагирует правильно: retry при 500, dead-letter при невалидном КМ, корректный переход при успехе.
End-to-end тесты на тестовом контуре ГИС МТ
Раз в неделю CI запускает тесты против тестового контура Честного ЗНАКа. Этот контур нестабилен — он падает чаще, чем продакшен — но именно это делает тесты ценными. Если наш код выживает на тестовом контуре, он точно выживет в продакшене. Тест проходит полный цикл: эмиссия тестового КМ, ввод в оборот, агрегация, выбытие. Если хотя бы один этап не проходит — сборка падает, и команда разбирается до деплоя.
Миграция статусов: что делать, когда меняется бизнес-процесс
Статусная машина не статична. Бизнес меняется: появляются новые категории маркировки, новые требования ГИС МТ, новые этапы на складе. Когда мы добавляли поддержку агрегатов второго уровня (паллеты, содержащие короба, содержащие коды), нам пришлось расширить машину новыми статусами и переходами.
Мы используем подход, при котором старые статусы не удаляются, а помечаются deprecated. Новые коды проходят через новую цепочку, старые — через старую. Это позволяет избежать массовой миграции данных в базе, которая рискованна для продакшена. Через несколько месяцев, когда все старые коды выбудут из оборота, deprecated-статусы можно убрать из Workflow.
Для миграции конфигурации Workflow мы используем версионирование: каждая версия — отдельный YAML-файл. При деплое система проверяет, какая версия активна, и загружает соответствующую конфигурацию. Откат на предыдущую версию — это просто смена одной строки в конфиге, а не откат кода.
Итог
Статусная машина кодов маркировки — это не избыточная сложность. Это минимально необходимая архитектура для любого бизнеса, который работает с маркировкой в объёмах, где ручной контроль невозможен. Она защищает от продажи неготовых кодов, от агрегации частичных наборов, от потери кодов в таймаутах ГИС МТ.
Коннектор 1С и Честный ЗНАК, построенный на Symfony с использованием Workflow, Messenger и Doctrine, даёт эту защиту из коробки. Он масштабируется с ростом каталога, тестируется unit-тестами и интегрируется с существующей 1С через OData без изменения конфигурации. Если ваш бизнес уже столкнулся с первыми штрафами за маркировку или с расхождениями между складом и ГИС МТ — это сигнал, что пора переходить от ручного управления к статусной машине.