Проверка кода маркировки в ГИС МТ из 1С без входа по сертификату: розничный токен и типовая функция ИС МП
Бесплатные
ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.
Узнавайте о новых бесплатных решениях в нашей телеграм-группе Инфостарт БЕСПЛАТНО
Вы можете заказать платную доработку или адаптацию этой разработки под вашу конфигурацию на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.
Обычная проверка кода маркировки из 1С идёт через ключ сессии ГИС МТ. Чтобы его получить, нужен сертификат электронной подписи с доступом к закрытому ключу на этом компьютере, а часто ещё и пароль к нему. У кладовщика, менеджера или фонового задания такого сертификата обычно нет. Поэтому простой вопрос «этот код вообще в обороте?» уходит к одному человеку в компании, у которого стоит подпись руководителя.
При этом касса проверяет каждый код при продаже, и подписью для этого не пользуется. Для разрешительного режима у розницы есть отдельный канал: запрос к CDN-площадкам ГИС МТ по розничному токену. Токен выдаётся один раз при настройке разрешительного режима и дальше работает без подписи. В конфигурациях с подсистемой ИС МП (УТ 11, КА 2, ERP, Розница) этот канал уже реализован в типовом коде, и вызвать его можно не только из чека.
Что умеет каждый канал
| Ключ сессии (cises/info) | Розничный токен (codes/check) | |
|---|---|---|
| Что нужно | Сертификат ЭП и вход в ИС МП | Настроенный разрешительный режим |
| Статус кода | Полный: эмитирован, в обороте, выбыл и т.д. | Признаки: найден, в обороте, продан, выведен |
| Владелец | ИНН и наименование | Только «владелец — эта организация: да/нет» |
| Срок годности | Есть | Есть |
| Блокировка кода | Есть | Есть |
| Скорость | Зависит от пачки и ключа | Около 65 мс на код: 200 кодов за 13 секунд, тысяча — около минуты |
Для большинства вопросов со склада розничного канала хватает: можно ли это продать, наш ли это код, не истёк ли срок. ИНН чужого владельца он не отдаёт, и для разбора «кто нам прислал чужие коды в УПД» по-прежнему нужен ключ сессии.
Где функция и как понять, что она есть
Нужная функция лежит в общем модуле ИнтерфейсИСМПОбщегоНазначения:
Ответ = ИнтерфейсИСМПОбщегоНазначения.СокращеннаяИнформацияПоКМПриРозничнойПродаже(МассивСтрок, Организация, "");
Её вызывает сам чек ККМ, поэтому параметры у неё «кассовые». МассивСтрок — массив строк таблицы значений с кодами (колонки ниже), Организация — та, для которой настроен токен. Третий параметр оставляем пустой строкой.
В старых релизах библиотеки модуля может не быть, поэтому перед вызовом проверяем метаданные: Метаданные.ОбщиеМодули.Найти("ИнтерфейсИСМПОбщегоНазначения") <> Неопределено. Мы отлаживали всё на УТ 11.5.
Какие строки ждёт функция
| Колонка | Что положить |
|---|---|
| Штрихкод, НормализованныйШтрихкод | Код без криптохвоста: 01 + GTIN + 21 + серийный номер |
| ПолныйКодМаркировки | Тот же код в Base64 (UTF-8, без переносов строк) |
| ВидПродукции, ПредставлениеНоменклатуры, ТекстОшибки | Можно оставить пустыми |
| КонтролиРазрешительногоРежима | Новый Массив |
Код в Excel или в УПД приходит в разном виде: со скобками (01)…(21)…(91)…, с разделителем GS (символ 29) или одной строкой с хвостом 91/92/93. Перед отправкой его приводим к виду «01 + 14 цифр GTIN + 21 + серийный номер». Если отправить код с хвостом как есть, площадка часто отвечает ошибкой по этому коду.
Что приходит в ответ
Функция возвращает структуру. Сначала смотрим на служебные признаки, по каждому из них ответа по кодам не будет:
| Поле ответа | Что значит и что делать |
|---|---|
| ТребуетсяОбновлениеКлючаСессии = Истина | Розничного токена нет: разрешительный режим для этой организации не настроен |
| ОтсутствуютCDNПлощадки = Истина | Список площадок пуст или устарел. Вызвать АктуализацияСпискаCDNПлощадок(Организация) из того же модуля и повторить запрос |
| ТребуетсяОбновлениеCDNПлощадок = Истина | Площадка не ответила, типовой код её пометил. Обновить список и повторить: запрос уйдёт на другую |
| АварийныйРежим = Истина | В ГИС МТ объявлен аварийный режим, проверка недоступна |
| РезультатыОтправкиЗапросов | Массив ответов площадок: КодСостояния и ТекстВходящегоСообщенияJSON |
Типовой код сам разбирает ответ, но отбрасывает код целиком, если по нему пришёл errorCode, хотя статус и срок годности в теле ответа при этом есть. Поэтому мы читаем ТекстВходящегоСообщенияJSON сами: в нём массив codes, по элементу на каждый код.
| Поле элемента codes | Смысл |
|---|---|
| cis | Код, по которому ответ (иногда короче запрошенного, сравниваем по началу) |
| found | Код есть в ГИС МТ |
| realizable | В обороте, можно продавать |
| sold | Уже продан (выбыл через кассу) |
| utilised | Нанесён, но в оборот не введён |
| isOwner | Владелец кода — организация из запроса |
| expireDate (в части ответов expirationDate) | Срок годности, строка вида 2026-11-12… |
| isBlocked | Код заблокирован |
| gtin | GTIN товара |
Мы приводим эти признаки к привычным статусам: sold → «выбыл», realizable → «в обороте», utilised → «не введён в оборот», найден, но ни одного признака нет → «эмитирован», found = Ложь → «не найден». Тогда отчёт по обоим каналам можно строить одним кодом.
Пример: проверить массив кодов
Функция возвращает соответствие «код → структура (Статус, НашКод, СрокГодности, Заблокирован, Ошибка)». Коды отправляются пачками по 50: на больших пачках площадка чаще не успевает ответить. Если площадка молчит, список площадок обновляется и делается одна повторная попытка.
Функция ПроверитьКодыПоРозничномуТокену(Коды, Организация) Экспорт
ИтогПроверки = Новый Соответствие;
Если Метаданные.ОбщиеМодули.Найти("ИнтерфейсИСМПОбщегоНазначения") = Неопределено Тогда
ВызватьИсключение "В конфигурации нет проверки кодов разрешительного режима";
КонецЕсли;
Модуль = Вычислить("ИнтерфейсИСМПОбщегоНазначения");
// Типовая проверка пишет журнал проверок КМ и состояние площадок —
// у пользователя может не быть прав на эти регистры.
УстановитьПривилегированныйРежим(Истина);
Попытка
ПроверитьВсеПачки(Модуль, Коды, Организация, ИтогПроверки);
Исключение
УстановитьПривилегированныйРежим(Ложь);
ВызватьИсключение;
КонецПопытки;
УстановитьПривилегированныйРежим(Ложь);
Возврат ИтогПроверки;
КонецФункции
Процедура ПроверитьВсеПачки(Модуль, Коды, Организация, Результат)
Пачка = Новый Массив;
Для Индекс = 0 По Коды.ВГраница() Цикл
Пачка.Добавить(КодБезХвоста(Коды[Индекс]));
Если Пачка.Количество() < 50 И Индекс < Коды.ВГраница() Тогда
Продолжить;
КонецЕсли;
Ответы = Неопределено;
Для Попытка_ = 1 По 2 Цикл
Ответы = ПроверитьПачку(Модуль, Пачка, Организация);
Если Ответы.Количество() > 0 Тогда
Прервать;
КонецЕсли;
Модуль.АктуализацияСпискаCDNПлощадок(Организация);
КонецЦикла;
Для Каждого Код Из Пачка Цикл
Результат.Вставить(Код, РазобратьЭлемент(Ответы.Получить(Код)));
КонецЦикла;
Пачка = Новый Массив;
КонецЦикла;
КонецПроцедуры
Функция ПроверитьПачку(Модуль, Пачка, Организация)
Таблица = Новый ТаблицаЗначений;
Для Каждого Имя Из СтрРазделить("Штрихкод,ПолныйКодМаркировки,НормализованныйШтрихкод,ВидПродукции,"
+ "ПредставлениеНоменклатуры,ТекстОшибки,КонтролиРазрешительногоРежима", ",") Цикл
Таблица.Колонки.Добавить(Имя);
КонецЦикла;
МассивСтрок = Новый Массив;
Для Каждого Код Из Пачка Цикл
Строка_ = Таблица.Добавить();
Строка_.Штрихкод = Код;
Строка_.НормализованныйШтрихкод = Код;
Base64 = Base64Строка(ПолучитьДвоичныеДанныеИзСтроки(Код, КодировкаТекста.UTF8, Ложь));
Строка_.ПолныйКодМаркировки = СтрЗаменить(СтрЗаменить(Base64, Символы.ВК, ""), Символы.ПС, "");
Строка_.ПредставлениеНоменклатуры = "";
Строка_.ТекстОшибки = "";
Строка_.КонтролиРазрешительногоРежима = Новый Массив;
МассивСтрок.Добавить(Строка_);
КонецЦикла;
Ответ = Модуль.СокращеннаяИнформацияПоКМПриРозничнойПродаже(МассивСтрок, Организация, "");
Если Поле(Ответ, "ОтсутствуютCDNПлощадки") = Истина Тогда
Модуль.АктуализацияСпискаCDNПлощадок(Организация);
Ответ = Модуль.СокращеннаяИнформацияПоКМПриРозничнойПродаже(МассивСтрок, Организация, "");
КонецЕсли;
Если Поле(Ответ, "ТребуетсяОбновлениеКлючаСессии") = Истина Тогда
ВызватьИсключение "Нет розничного токена: разрешительный режим для организации не настроен";
КонецЕсли;
Если Поле(Ответ, "АварийныйРежим") = Истина Тогда
ВызватьИсключение "В ГИС МТ объявлен аварийный режим";
КонецЕсли;
// Разбираем тела ответов сами: типовой разбор отбрасывает коды с errorCode.
Данные = Новый Соответствие;
Результаты = Поле(Ответ, "РезультатыОтправкиЗапросов");
Если Результаты = Неопределено Тогда
Возврат Данные;
КонецЕсли;
Для Каждого РезультатЗапроса Из Результаты Цикл
ТекстJSON = Поле(РезультатЗапроса, "ТекстВходящегоСообщенияJSON");
Если Поле(РезультатЗапроса, "КодСостояния") <> 200 Или Не ЗначениеЗаполнено(ТекстJSON) Тогда
Продолжить;
КонецЕсли;
Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(ТекстJSON);
Тело = ПрочитатьJSON(Чтение, Истина);
Чтение.Закрыть();
КодыОтвета = Поле(Тело, "codes");
Если ТипЗнч(КодыОтвета) <> Тип("Массив") Тогда
Продолжить;
КонецЕсли;
Для Каждого Элемент Из КодыОтвета Цикл
КодОтвета = Строка(Поле(Элемент, "cis"));
Если ПустаяСтрока(КодОтвета) Тогда
Продолжить;
КонецЕсли;
// cis в ответе бывает короче запрошенного — сравниваем по началу в обе стороны.
Для Каждого Код Из Пачка Цикл
Если СтрНачинаетсяС(Код, КодОтвета) Или СтрНачинаетсяС(КодОтвета, Код) Тогда
Данные.Вставить(Код, Элемент);
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
Возврат Данные;
КонецФункции
// Ответ типовой функции — Структура, тело JSON — Соответствие: читаем поле одинаково.
Функция Поле(Коллекция, Ключ)
Если ТипЗнч(Коллекция) = Тип("Соответствие") Тогда
Возврат Коллекция.Получить(Ключ);
ИначеЕсли ТипЗнч(Коллекция) = Тип("Структура") И Коллекция.Свойство(Ключ) Тогда
Возврат Коллекция[Ключ];
КонецЕсли;
Возврат Неопределено;
КонецФункции
Функция РазобратьЭлемент(Элемент)
Итог = Новый Структура("Статус, НашКод, СрокГодности, Заблокирован, Ошибка", "", Ложь, Дата(1, 1, 1), Ложь, "");
Если Элемент = Неопределено Тогда
Итог.Ошибка = "площадка не вернула ответ по коду";
ИначеЕсли Элемент.Получить("found") = Ложь Тогда
Итог.Статус = "не найден";
Иначе
Итог.Статус = ?(Элемент.Получить("sold") = Истина, "выбыл",
?(Элемент.Получить("realizable") = Истина, "в обороте",
?(Элемент.Получить("utilised") = Истина, "не введён в оборот", "эмитирован")));
Итог.НашКод = (Элемент.Получить("isOwner") = Истина);
Итог.Заблокирован = (Элемент.Получить("isBlocked") = Истина);
Срок = Элемент.Получить("expireDate");
Если Не ЗначениеЗаполнено(Срок) Тогда
Срок = Элемент.Получить("expirationDate");
КонецЕсли;
Если ЗначениеЗаполнено(Срок) Тогда
Итог.СрокГодности = Дата(Число(Лев(Срок, 4)), Число(Сред(Срок, 6, 2)), Число(Сред(Срок, 9, 2)));
КонецЕсли;
КонецЕсли;
Возврат Итог;
КонецФункции
// (01)GTIN(21)серия(91)…, GTIN+серия+GS+хвост или одной строкой U94; 01 + GTIN + 21 + серия.
Функция КодБезХвоста(Знач Код)
Код = СокрЛП(Код);
Если СтрНачинаетсяС(Код, "(01)") Тогда
Код = СтрЗаменить(СтрЗаменить(Код, "(01)", "01"), "(21)", "21");
Позиция = СтрНайти(Код, "(");
Возврат ?(Позиция > 0, Лев(Код, Позиция - 1), Код);
КонецЕсли;
Позиция = СтрНайти(Код, Символ(29));
Если Позиция > 0 Тогда
Возврат Лев(Код, Позиция - 1);
КонецЕсли;
Если СтрДлина(Код) > 31 И СтрНачинаетсяС(Код, "01") И Сред(Код, 17, 2) = "21" Тогда
Возврат Лев(Код, 31); // серийный номер из 13 знаков, как у большинства товарных групп
КонецЕсли;
Возврат Код;
КонецФункции
Замер на УТ 11.5: 6 кодов (одна пачка) — 1,4 секунды, 200 кодов (четыре пачки) — 12,7 секунды, то есть около 64 мс на код. Большая часть времени уходит на ожидание ответа площадки, поэтому мелкие пачки выгоднее собирать в одну.
Последняя ветка КодБезХвоста рассчитана на серийный номер из 13 знаков. У части товарных групп длина другая, и для них код лучше брать уже с разделителем GS или в формате со скобками.
Пример рассчитан на выполнение на сервере: в нём привилегированный режим и обращение к общему модулю через Вычислить, чтобы код компилировался и в конфигурациях без ИС МП.
Сроки годности: то, ради чего всё затевалось
Срок годности в ответе — главная причина, по которой мы вообще полезли в этот канал. В учёте 1С срока у кода маркировки обычно нет, а ГИС МТ его знает: производитель указывает срок при вводе в оборот. Значит, можно взять все коды на остатке склада, прогнать через розничный канал и получить список «истёк» и «истекает в ближайшие N дней», отсортированный по дате.
На тестовой копии нашей розничной базы агент проверил 3670 кодов на остатках. У двух срок уже истёк — антифриз и масло для АКПП, оба числились в продаже. Ещё у 15 срок истекает в ближайшие месяцы. Без проверки просрочку нашли бы только на кассе или уже у покупателя. Срок в ответе ГИС МТ пришёл по 3636 кодам из 3670: по остальным товарным группам его не передают.
Названия товаров и складов на скриншотах изменены.
Срок есть не у всех товарных групп. Если в ответе нет expireDate, это не ошибка: для этого товара ГИС МТ срок не хранит.
Где это ломается
- Не та организация. Токен настраивается на организацию. Если в базе несколько организаций, а токен есть только у одной, запрос от остальных вернёт
ТребуетсяОбновлениеКлючаСессии. Можно перебрать организации базы и взять первую, у которой проверка прошла. - «Не наш» код не значит «поставщик ошибся».
isOwnerсравнивает владельца с организацией из запроса. Если товар числится на другой вашей организации (например, на ИП из той же группы компаний), канал скажет «не владелец», а ИНН не покажет. Не делайте из этого вывод «поставщик не передал коды», пока не проверили по ключу сессии. - Нет прав у пользователя. Типовая функция пишет журнал проверок кодов и состояние площадок. У рядового пользователя прав на эти регистры может не быть, отсюда привилегированный режим в примере.
- Площадки иногда молчат. Ответ без единого кода — это не «коды не найдены». Обновите список площадок и повторите запрос, а если не вышло, честно пишите «ГИС МТ не ответила».
Не нажимайте «Выполнить обмен» в копии рабочей базы
Эта проверка только читает: в ГИС МТ ничего не регистрируется и не отправляется. Но если вы отлаживаете её на копии рабочей базы, рядом в форме ИС МП есть кнопка «Выполнить обмен», и она уже не про чтение.
В копии флажок работы с тестовым контуром (константа РежимРаботыСТестовымКонтуромИСМП) обычно выключен, потому что он был выключен в рабочей базе. Копия считает себя боевой и ходит в настоящий ГИС МТ. «Выполнить обмен» отправит всю очередь накопившихся документов ИС МП от имени ваших организаций. Мы на этом почти попались: спасло то, что в копии не был задан пароль к сертификату и подписание не прошло. Перед отладкой маркировки в копии включите тестовый контур или хотя бы проверьте эту константу.
Где мы это используем
Сначала мы сделали такую проверку для кладовщиков в собственном расширении: проверять срок годности при подборе заказа. Потом перенесли её в ИИ-агента, который работает внутри 1С как чат. Если у агента нет ключа сессии ИС МП, он проверяет коды по розничному токену и пишет, по какому каналу проверено. Пользователь спрашивает «что на складе с истекающим сроком годности за 60 дней?» и получает список по складам: сначала то, что истекает раньше, а номер документа в ответе открывается щелчком.
Расширение называется ИИ Агент в 1С. Если нужна только проверка кодов, код из статьи работает сам по себе в любой конфигурации с ИС МП и настроенным разрешительным режимом.
Что приложено к статье
В архиве к статье два файла и инструкция, всё бесплатно:
- ПроверкаКМРозница.epf — внешняя обработка с кодом из статьи. Выбираете организацию, вставляете коды и получаете по каждому статус, «наш ли код», срок годности и время проверки.
- agent1c-1.0.22-infostart.cfe — расширение ИИ Агент в 1С. Его можно попробовать бесплатно в приложенном файле: после установки нажмите «Начать работу», три дня агент работает без оплаты. Спросите его «что на складе с истекающим сроком годности за 60 дней?» на своей базе.
Вступайте в нашу телеграмм-группу Инфостарт