Статусная машина кодов маркировки: как устроен коннектор 1С ↔ Честный ЗНАК

Код маркировки проходит через десяток статусов от эмиссии до выбытия. Каждый статус — это точка интеграции с ГИС МТ, и каждая точка может дать сбой. Разбираем, как построить коннектор 1С и Честный ЗНАК, который не теряет коды в промежуточных состояниях и не пытается отправить в продажу то, что ещё не эмитировано.

Маркировка перестала быть опцией. С 2024 года под неё попадают всё новые категории товаров, а с 2026 года — практически весь розничный оборот. Для бизнеса это означает не просто покупку принтера этикеток. Это означает, что в ERP должна появиться полноценная статусная машина кодов маркировки, которая отслеживает каждый КМ от момента заказа у оператора до момента выбытия из оборота. Без такой машины склад начинает продавать товар, которого в ГИС МТ ещё нет, а бухгалтерия получает штрафы за расхождения.

Код маркировки — это не просто строка в базе данных. Это объект с жизненным циклом, который живёт параллельно в трёх системах: вашей 1С, принтере этикеток и ГИС МТ. Если статусы в этих системах рассинхронизируются — бизнес получает проблемы, которые сложно диагностировать и дорого исправлять.

Зачем нужна статусная машина в интеграции с Честным ЗНАКом

Без статусной машины код маркировки — это просто поле в таблице. Вы не знаете, заказан ли он у оператора, напечатана ли этикетка, агрегирован ли в короб, передан ли в магазин, продан ли покупателю или списан. Каждый из этих переходов требует обращения к ГИС МТ, и каждое обращение может завершиться ошибкой, задержкой или таймаутом.

Статусная машина решает три задачи. Первая — она не позволяет совершить операцию, для которой код ещё не готов. Нельзя агрегировать коды в короб, если они ещё в статусе «эмиссия заказана, но не подтверждена». Нельзя продать товар, если код не прошёл этап ввода в оборот. Вторая задача — она позволяет отследить, на каком этапе застрял конкретный код, когда произошло это и почему. Третья — она даёт точку входа для 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 часа.

Dead-letter очередь

Коды, которые исчерпали 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 без изменения конфигурации. Если ваш бизнес уже столкнулся с первыми штрафами за маркировку или с расхождениями между складом и ГИС МТ — это сигнал, что пора переходить от ручного управления к статусной машине.