Схема заявки на карточку товара в государственном каталоге состоит из 23 атрибутов, восемь из них обязательные, и узнать это можно только одним способом: спросить у каталога. Список, который я перенёс из работающей боевой реализации, содержал два кода, которых в живой схеме нет вовсе. Заявки при этом уходили и принимались, поэтому расхождение никак себя не проявляло.
Ниже разбор того, как выглядит работа с госкаталогом товаров из 1С со стороны кода: два разных входа, один из которых вообще не требует ключа, живая схема атрибутов вместо зашитого списка, подбор категории по названию товара и десяток мест, где очевидное решение оказывается неверным. Речь про казахстанский Национальный каталог, но устроен он так же, как российский: карточка, категория из классификатора, набор атрибутов под категорию, модерация. Механика переносится, меняются имена полей.
Что вообще требуется
С 1 января 2026 года код Национального каталога обязателен для всех участников оборота товаров в Казахстане, с 1 июля он попадает в чек. Код выдаётся карточке товара в каталоге. Если карточки нет, кода взять неоткуда.
Отсюда две разные задачи, которые постоянно путают:
| Задача | Когда возникает | Что нужно от каталога |
| проставить код в базе | карточка в каталоге уже есть, её завёл производитель или импортёр | найти по штрихкоду и забрать код |
| завести карточку | карточки нет ни у кого | подобрать категорию, собрать атрибуты, отправить заявку на модерацию |
Первая задача решается поиском по штрихкоду и занимает страницу кода. Вторая упирается в то, что каталог не примет заявку без категории из классификатора и без обязательных атрибутов этой категории, а ни того, ни другого в учётной базе нет.
Два входа, и один из них открыт
У каталога два независимых интерфейса, и это первое, что стоит знать перед проектированием.
| Вход | Авторизация | Что отдаёт |
публичный поиск, nct.gov.kz |
никакой | поиск товаров по штрихкоду и по наименованию, коды NTIN, признак деактивации |
кабинет, nationalcatalog.kz/gwp/portal/api/v1 |
заголовок X-API-KEY | схема атрибутов, список заявок, карточка по коду, создание заявки |
Публичный вход не требует ни ключа, ни регистрации, ни электронной подписи. Обычный HTTPS-запрос с параметрами. Через него можно узнать, есть ли товар в каталоге, ещё до того, как компания получит доступ в кабинет, и на нём же держится весь подбор категории, о котором ниже.
Ключ кабинета устроен приятно просто: постоянное значение в заголовке, без логина, пароля, токенов и их обновления.
Заголовки = Новый Соответствие;
Заголовки.Вставить("X-API-KEY", КлючAPI);
Заголовки.Вставить("Accept", "application/json");
Первая грабля: сервер отдаёт gzip, платформа получает мусор
Публичный поиск по умолчанию сжимает ответ. Объект HTTPСоединение в 1С этого не разбирает, и ПолучитьТелоКакСтроку("UTF-8") возвращает нечитаемые байты. Симптом выглядит как битая кодировка, и полдня уходит на игры с кодировками, которые тут ни при чём.
Лечится одним заголовком:
Заголовки.Вставить("Accept-Encoding", "identity");
На кабинет это не распространяется, он отвечает без сжатия. Отличие двух входов по одному заголовку и есть причина, по которой их удобно держать разными функциями транспорта. Одна универсальная тут только мешает.
Вторая грабля: параметры запроса надо кодировать
В боевой реализации, с которой я начинал, параметры подставлялись в строку пути конкатенацией. Работало до первого наименования с амперсандом. Товар вида "Кабель HDMI 2.0 4K&60Гц" превращает один параметр в два, каталог видит обрезанный запрос и честно отвечает, что ничего не нашёл. Никакой ошибки при этом нет, просто пустой результат, который читается как "такого товара в каталоге не существует".
Части.Добавить(КодироватьСтроку(Строка(КЗ.Ключ), СпособКодированияСтроки.КодировкаURL)
+ "=" + КодироватьСтроку(Строка(КЗ.Значение), СпособКодированияСтроки.КодировкаURL));
Класс ошибки тут важнее самой ошибки. Отрицательный ответ от внешней системы одинаково означает "нет данных" и "я задал вопрос неправильно", и по виду ответа эти два случая не различаются.
Схему атрибутов надо спрашивать каждый раз
Каталог отдаёт схему заявки отдельным методом. Ответ содержит список атрибутов: код, наименование, признак обязательности, тип. На момент замера 07.09.2026 в схеме 23 атрибута, обязательных восемь.
| Код атрибута | Что это | Откуда берём в 1С |
| name_ru | наименование на русском | Номенклатура.НаименованиеПолное |
| name_kk | наименование на казахском | отдельный реквизит, в типовых его нет |
| short_name_ru | краткое наименование | усечённое наименование |
| country | страна происхождения | СтранаПроисхождения.КодАльфа2 |
| measure_unit | единица измерения | ЕдиницаПоКлассификатору.Код |
| quantity | количество в единице | реквизит единицы |
| tnved | код ТН ВЭД | КодТНВЭД, очищенный до цифр |
| a4282e5d | наименование производителя | ОсновнойПоставщик |
Последняя строка объясняет, почему схему приходится читать. Код атрибута под производителя выглядит как a4282e5d, и никакой логики в нём нет: это идентификатор в справочнике каталога. Зашить такой код в исходник можно, но проверить его глазами нельзя, а меняется он без предупреждения.
Список, который я перенёс из прежней реализации, содержал unit_category и made_in_country. В живой схеме их нет. Заявки уходили, каталог принимал их и молчал про лишние поля, поэтому расхождение жило незамеченным всё время работы прежнего кода. Обнаружилось оно ровно в тот момент, когда я вместо переноса списка спросил схему.
Отдельно проверил, зависит ли схема от категории: она одна на все ОКТРУ. Значит читать её достаточно один раз за прогон, сразу на все товары. Если каталог когда-нибудь сделает схему покатегорийной, поведение придётся пересматривать, и это записано в ограничениях явно.
Справочные атрибуты хотят код страны
Атрибут country принимает CN, но отвергает "Китай". Это выглядит очевидным ровно до момента, когда в базе лежит СтранаПроисхождения ссылкой, а её представление это как раз название. Строка кода отличается на одно поле:
// неверно: уйдёт название
Значение = Строка(Данные.СтранаПроисхождения);
// верно: уйдёт код
Значение = Данные.СтранаПроисхождения.КодАльфа2;
С единицей измерения та же история: каталог ждёт код ОКЕИ, для штуки это 796, и берётся он из ЕдиницаХраненияОстатков.ЕдиницаПоКлассификатору.Код. Честно скажу, что принимает ли каталог именно этот формат, я не подтверждал вызовом, и в документации инструмента это помечено как непроверенное.
ТН ВЭД в базах хранится по-разному: с пробелами, с точками, группами по четыре цифры. Каталог ждёт десять цифр подряд, поэтому значение чистится до цифр перед отправкой.
Категория: подбор, который угадывает 7 раз из 10
Самая интересная часть. Заявка требует код ОКТРУ, это категория четвёртого уровня отраслевого классификатора. В учётной базе такого поля нет и взяться ему неоткуда: номенклатура разложена по своим группам, придуманным в компании.
Идея обходного пути такая. Взять первое значащее слово наименования, найти этим словом похожие товары в публичном поиске, запросить их карточки в кабинете и посмотреть, в какой категории они лежат. Карточка отдаёт поле categoryAncestors с деревом предков, четвёртый уровень и есть искомый ОКТРУ.
Для Каждого Предок Из Предки Цикл
Если ЗначениеПоля(Предок, "level", 0) <> 4 Тогда
Продолжить;
КонецЕсли;
Код = СокрЛП(Строка(ЗначениеПоля(Предок, "code", "")));
ИмяКатегории = Строка(ЗначениеПоля(Предок, "nameRu", ""));
КонецЦикла;
Первая версия требовала, чтобы слово из наименования встречалось в названии найденной категории. Логика понятная: совпало слово, значит попали. На первой же выборке эта версия подобрала код у нуля товаров из трёх.
Причина в том, что требование невыполнимо по построению. Наименования вида "LED-панель 40Вт" или "Наушники TWS" дают первым значащим словом латиницу, а названия категорий классификатора написаны по-русски и латинских слов не содержат никогда. Резолвер отказывал в тот момент, когда категорию уже нашёл: находил подходящую и сам же её отбрасывал.
Правку сделал минимальную: совпадение слова стало предпочтением. Если слово встретилось в названии категории, код берётся сразу. Если нет, запоминается первая встреченная категория четвёртого уровня и отдаётся как приблизительная. На той же выборке из десяти наименований подбор стал давать код у семи.
| Версия правила | Результат на выборке |
| совпадение слова обязательно | 0 из 3 |
| совпадение слова это предпочтение | 7 из 10 |
Важная оговорка, которую я вынес и в интерфейс, и в документацию: это подсказка. Категория ближайшего похожего товара может быть верной, а может и не быть, и решает человек. В таблице предпросмотра каждая строка помечена тем, как именно подобрался код, точным совпадением или приблизительно, и строки второго типа надо просматривать глазами. Инструмент, который делает вид, что классифицировал номенклатуру, вреднее того, который честно говорит "вот моя догадка".
Ещё одна деталь оттуда же: в классификаторе есть категория с названием "Без ОКТРУ". Формально она четвёртого уровня и подходит под все проверки. Отправлять товар туда бессмысленно, поэтому она отсеивается по имени.
Подбор медленный: на каждое новое слово уходит поиск плюс до пяти карточек, между обращениями пауза. Поэтому найденное складывается в кэш по слову, и второй прогон по тем же наименованиям идёт заметно быстрее. Кэш живёт в хранилище платформы, на пятистах строках это единицы килобайт.
Один штрихкод, несколько кодов каталога
Поиск по GTIN возвращает не одну запись. Каталог хранит историю: карточка могла быть заведена дважды, потом погашена, потом заменена. У элемента ответа есть признак ntin_isdeactivated и поле ntin_duplicateofproduct, указывающее на актуальную карточку взамен погашенной.
Если Деактивирован Тогда
Дубль = СокрЛП(Строка(ЗначениеПоля(Элемент, "ntin_duplicateofproduct", "")));
Если ЗначениеЗаполнено(Дубль) Тогда
Возврат Дубль;
КонецЕсли;
КонецЕсли;
Возврат Код;
Правило выбора получилось такое: берём активную карточку, при нескольких активных более свежую по дате изменения, а если активной нет вовсе, идём по ссылке погашенной. Это рабочее правило, и спорные случаи всё равно надо смотреть глазами.
Что мешает в старых конфигурациях
Инструмент делался под УПП и УТП, то есть под обычные формы. Там нашлось несколько мест, где типовая структура данных сопротивляется.
Штрихкод не строка. В УПП поле штрихкода в регистре сведений это характеристика. Примитивного типа там нет. Запрос с обычным сравнением падает на "Неверные параметры ДЛИНАСТРОКИ". Лечится приведением прямо в тексте запроса:
ВЫРАЗИТЬ(ШК.Штрихкод КАК СТРОКА(200)) КАК Штрихкод
Владелец составного типа. Измерение регистра штрихкодов в УПП принимает несколько типов. Если отбирать набор записей только по штрихкоду, в набор попадут чужие строки, и типовой обработчик записи упадёт уже на них. Отбор ставится и по владельцу тоже.
Ведущая табуляция в данных. Часть штрихкодов в живых базах содержит символ табуляции в начале. Для запроса в каталог значение обрезается, но ключом регистра остаётся сырое: чинить сами данные инструмент не берётся, это отдельная работа с НСИ и решение владельца базы.
Реквизит с именем "Результат". Мелочь, стоившая часа. Реквизит обработки с таким именем перекрывает одноимённую локальную переменную в модуле объекта, и модуль перестаёт компилироваться с сообщением, которое указывает совсем в другое место.
Где выполняется HTTP
Момент, который стоит проверить до внедрения. В обычном приложении модуль объекта внешней обработки выполняется на клиенте. Значит запросы к каталогу уходят с компьютера пользователя, и доступ к внешним хостам нужен на рабочем месте, а не на сервере 1С. В управляемом приложении картина обратная.
Обойти это нельзя, поэтому оно вынесено в требования прямым текстом. Тихая версия этой проблемы выглядит так: у администратора всё работает, а у оператора в другом филиале запрос уходит в таймаут, и виноватым назначается инструмент.
Как обойтись без правки конфигурации
Отдельная цель, которую я себе поставил: конфигурация клиента не меняется вообще. Ни расширения, ни новых объектов, ни правки типовой. Причина утилитарная. Обновление УПП на поддержке с расширением превращается в отдельное мероприятие, и заказчик справедливо не хочет его ради выгрузки товаров.
Состояние живёт в штатном ХранилищеОбщихНастроек под именем объекта Tz_НКТ. Тонкость: имя пользователя передаётся пустым, и тогда настройки становятся общими для всей базы.
ХранилищаНастроек.ОбщиеНастройки.Сохранить("Tz_НКТ", Ключ, Значение, , "");
Туда же уезжают списки отбора в ХранилищеЗначения и кэш подобранных категорий. Имена метаданных нигде не зашиты: регистр штрихкодов, поля в нём, регистр остатков и реквизиты номенклатуры задаются настройками, а два специфичных реквизита ищутся по метаданным среди нескольких допустимых имён. Умолчания рассчитаны на УПП, там инструмент работает без единой настройки.
Цена такого решения одна, и она честно записана в ограничениях: ключ API лежит в базе незашифрованным. Безопасное хранилище есть в БСП, но БСП стоит не у всех, и требование не трогать конфигурацию оказалось жёстче.
Чего инструмент не делает
Список отказов у меня обычно длиннее списка возможностей, и это осознанно.
- Не чинит НСИ. Товар с пустым ТН ВЭД или страной попадёт в список проблемных со списком того, чего не хватает.
- Не получает GTIN. Внутренние штрихкоды с префикса 2 каталогу не годятся, настоящий код выдаёт GS1 Казахстан.
- Не режет значения по длине. Если код не помещается в поле базы, работа останавливается с внятной ошибкой: молчаливая обрезка это порча данных.
- Не отзывает и не правит созданные заявки.
- Не решает за человека, верна ли подобранная категория.
Отдельно про лимит выборки. Он есть, по умолчанию снят, и когда задан, печатается в строке отбора вместе со всем остальным. Молча усечённая выборка выглядит как "в базе больше нет товаров", и это дороже длинного прогона.
Что проверено вызовом, а что нет
Слово "проверено" я ставлю по результату вызова, а не по логике кода. Поэтому статус выглядит так.
| Что | Статус |
| схема атрибутов и структура карточки | проверено 07.09.2026, схема отвечает 200, снято 23 атрибута |
| отбор по складу через остатки | проверено на живой УПП, отобран 531 товар |
| подбор категории | проверено, 7 из 10 наименований |
| сборка атрибутов по живой схеме | проверено |
| создание заявки в каталоге | проверено 08.09.2026 на одной карточке |
| формат единицы измерения | не подтверждено |
| запуск по расписанию | не проверялось |
Две последние строки могли бы в статью не попасть, и никто бы не заметил. Мне кажется, что список непроверенного полезнее списка возможностей: он говорит читателю, где именно придётся проверять самому.
Итог
Главный вывод получился методический. Интеграция с государственным реестром выглядит как задача про HTTP, а на деле оказывается задачей про то, откуда брать данные, которых в учётной базе нет: классификатор отраслевых категорий, наименование на втором государственном языке, товарный знак отдельным полем. Транспорт пишется за день, а вот подбор категории и честная разметка того, где инструмент угадывает, заняли больше времени, чем всё остальное вместе.
И отдельно про перенос кода. Работающая боевая реализация врала мне ровно в одном месте, зато молча: два несуществующих атрибута отправлялись годами и никому не мешали. Заменить перенос списка на запрос схемы стоило одного вызова.
Готовый инструмент со всем описанным лежит в карточке рядом: Выгрузка товаров в Национальный каталог из 1С. Обычные формы, УПП и УТП, платформа 8.3.20 и выше, конфигурация не меняется.
Другие наши инструменты, которые пригодятся рядом с этой задачей:
- Сводная таблица в Excel из 1С без COM - если карточки всё-таки заводятся штатным путём, через выгрузку и ручную загрузку на портал, файл надо чем-то собирать, и COM на сервере обычно недоступен.
- Выгрузка метаданных для LLM - чтобы понять, где в чужой доработанной базе лежат штрихкоды, остатки и реквизиты номенклатуры, прежде чем настраивать привязку.
- Анализ кода внешних обработок - когда в базе уже крутится чья-то обработка по каталогу и непонятно, что именно она делает с данными.
Вступайте в нашу телеграмм-группу Инфостарт