Приветствуем вас! Сегодня мы с вами разберем одну из важных задач при работе с маркированными товарами — программное списание кодов маркировки (КМ) в системе "Честный ЗНАК" (ЧЗ) непосредственно из вашей конфигурации 1С. Эта процедура необходима, когда товар выбывает из оборота не через розничную продажу, и требует точного учета и соблюдения законодательных требований. Мы подробно рассмотрим, как сформировать и отправить необходимые данные через API "Честного ЗНАКа", используя примеры кода 1С.
Мы выясним, что списание кодов маркировки требуется во многих случаях, когда товар перестает быть частью вашего товарного оборота, но не продается через кассу. Давайте рассмотрим наиболее распространенные причины: * **Безвозмездная передача:** например, для рекламных целей или в качестве подарка. * **Дистанционная продажа:** когда товар отправляется покупателю без использования стандартной кассы. * **Использование для производственных или собственных нужд:** когда маркированный товар используется как сырье или для внутренних потребностей компании. * **Порча, утеря, истечение срока годности:** если товар стал непригоден или был утрачен. * **Конфискация, утилизация, уничтожение:** принудительное или добровольное выведение товара из оборота. * **Экспорт за пределы ЕАЭС:** при вывозе товара из стран Евразийского экономического союза. * **Продажа по госконтракту, по образцам, по сделке с гостайной, через вендинговый аппарат.** * **Другие причины:** если ни одна из перечисленных причин не подходит, всегда можно указать "Другое" и подробно описать ситуацию. Для осуществления списания через API вам понадобится усиленная квалифицированная электронная подпись (УКЭП), и, конечно же, вы должны быть зарегистрированы в системе "Честный ЗНАК". Сведения о списании можно подавать как через личный кабинет ЧЗ вручную, так и программно, используя True API. Именно второй вариант мы с вами и разберем.
Давайте проанализируем, как происходит взаимодействие с ЧЗ через API. Для создания документов списания используется единый метод `POST /lk/documents/create` для большинства товарных групп. Этот метод является актуальным и пришел на смену устаревающим. При формировании запроса в формате JSON для списания, мы должны указать следующие ключевые данные: * **ИНН участника оборота** (`inn`). * **Причина вывода из оборота** (`action`): например, "PACKING" (фасовка/упаковка) или "EXPIRATION" (истечение срока годности). * **Дата операции** (`action_date`). * **Тип документа** (`document_type`): часто используется "OTHER". * **Номер и дата первичного документа** (`document_number`, `document_date`). * **Пользовательское наименование первичного документа** (`primary_document_custom_name`): например, "Акт списания товара". * В секции `products` мы указываем информацию о списываемых товарах. Здесь есть нюансы, зависящие от способа учета: * Для **поэкземплярного учета** (например, одежда, обувь, табак) указываются индивидуальные коды маркировки (`cis`). * Для **объемно-сортового учета (ОСУ)** (например, молочная продукция, упакованная вода для HoReCa) достаточно передать глобальный идентификатор товара (GTIN) и его количество (`gtin_quantity`). Теперь, когда мы понимаем общие принципы, перейдем к конкретному примеру реализации в 1С.
Мы рассмотрим процедуру ОтправитьЧерезАПИ(), которая реализует логику программного списания кодов маркировки.
Первым делом нам необходимо убедиться, что у нас есть действующий токен авторизации. Без него любое взаимодействие с API ЧЗ невозможно.
Если ПроверитьПолучитьТокен() = 0 Тогда
Если Вопрос("Отсутствует токен авторизации. Если это рабочее место имеет доступ к ЭЦП, нажмите ""Да"", иначе нажмите ""Нет"" и выполните проверку связи с ЦРПТ с рабочего места, имеющего доступ к ЭЦП.", 4, 15) = 6 Тогда
СтрОшибка = "";
ТекТокен = ПолучитьТокенЦРПТ(глПользователь.ЭЦП, ПрефиксВерсии, СтрОшибка);
Иначе
Записать();
СтатусВозврата(0);
Возврат;
КонецЕсли;
КонецЕсли;
Здесь мы видим, что сначала выполняется функция `ПроверитьПолучитьТокен()`. Если токен отсутствует, система предлагает получить его с помощью ЭЦП, используя функцию `ПолучитьТокенЦРПТ()`. Мы должны убедиться, что рабочее место имеет доступ к электронной подписи.
Далее мы приступаем к формированию данных, которые будут отправлены в "Честный ЗНАК". Для этого мы будем использовать объекты 1С `СписокЗначений` и `ТаблицаЗначений`.
ВремКат = КаталогВременныхФайлов();
СзЗаг = СоздатьОбъект("СписокЗначений");
СзТов = СоздатьОбъект("СписокЗначений"); // Это похоже на заготовку, но далее используется ТзТов
ТзТов = СоздатьОбъект("ТаблицаЗначений");
Мы создаем `СписокЗначений` под названием `СзЗаг` для хранения общих данных документа (заголовочная часть) и `ТаблицаЗначений` `ТзТов` для хранения информации о товарах.
Мы пошагово заполняем `СзЗаг` необходимыми полями:
СзЗаг.ДобавитьЗначение(СокрЛП(ВыделитьИНН(ЮрЛицо.ИНН)), "inn");
Если ПричВыб = Перечисление.ПричВыбМарки.Развес Тогда
СзЗаг.ДобавитьЗначение("PACKING", "action");
Иначе
СзЗаг.ДобавитьЗначение("EXPIRATION", "action");
КонецЕсли;
СзЗаг.ДобавитьЗначение(ПреобразоватьДатуВСтрХМЛ(ТекущаяДата()), "action_date");
СзЗаг.ДобавитьЗначение("OTHER", "document_type");
СзЗаг.ДобавитьЗначение(СокрЛП(Число(НомерДок)), "document_number");
СзЗаг.ДобавитьЗначение(ПреобразоватьДатуВСтрХМЛ(ДатаДок),"document_date");
Если ПричВыб = Перечисление.ПричВыбМарки.Развес Тогда
СзЗаг.ДобавитьЗначение("Акт фасовки товара", "primary_document_custom_name");
Иначе
СзЗаг.ДобавитьЗначение("Акт списания товара", "primary_document_custom_name");
КонецЕсли;
* `inn`: ИНН юридического лица. Мы используем функцию `ВыделитьИНН()` для извлечения ИНН. * `action`: Причина выбытия. Здесь мы видим примеры "PACKING" (фасовка) или "EXPIRATION" (истечение срока годности), которые зависят от значения `ПричВыб` (например, из перечисления `Перечисление.ПричВыбМарки`). * `action_date`: Дата операции, конвертируется в строковый формат XML. * `document_type`: Тип документа, в данном случае "OTHER" (прочий). * `document_number`, `document_date`: Номер и дата первичного документа. * `primary_document_custom_name`: Пользовательское наименование первичного документа, например, "Акт фасовки товара" или "Акт списания товара".
Теперь мы перейдем к заполнению информации о самих товарах. Здесь особенно важно учитывать способ учета – поэкземплярный или объемно-сортовой.
ВыбратьСтроки(); // Предполагается, что это метод документа, который позволяет перебрать его строки
Если ПричВыб = Перечисление.ПричВыбМарки.Развес Тогда
ТзТов.НоваяКолонка("cis");
ТзТов.НоваяКолонка("product_cost");;
Иначе
Если СпосУч = Перечисление.СпособУчета.ОСУ Тогда
ТзТов.НоваяКолонка("gtin");
ТзТов.НоваяКолонка("gtin_quantity");
Иначе
ТзТов.НоваяКолонка("cis");
КонецЕсли;
КонецЕсли;
Пока ПолучитьСтроку() = 1 Цикл
ТзТов.НоваяСтрока();
Марк = СокрЛП(Марка);
Если ПричВыб = Перечисление.ПричВыбМарки.Развес Тогда
ТзТов.cis = Марк;
Иначе
Если СпосУч = Перечисление.СпособУчета.ОСУ Тогда
// Извлечение GTIN из полного кода маркировки
Если СтрДлина(Марк) > 14 Тогда
ПозРазд = Найти(Марк, "21");
НачМарки = Лев(Марк, ПозРазд - 1);
GTIN = Прав(НачМарки, 14);
Иначе
GTIN = Марк;
КонецЕсли;
ТзТов.gtin = GTIN;
Иначе
ТзТов.cis = ПолучитьКИЗ(СтрЗаменить(Марка, "", Симв(29)));
КонецЕсли;
КонецЕсли;
Если ПричВыб = Перечисление.ПричВыбМарки.Развес Тогда
ТзТов.product_cost = ЦенаЕд * 100;
Иначе
Если СпГрупп.ТекущаяСтрока() = 1 Тогда // Возможно, имеется в виду группировка по GTIN
Если СпосУч = Перечисление.СпособУчета.ОСУ Тогда
ТзТов.gtin_quantity = Количество;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЦикла;
СзЗаг.ДобавитьЗначение(ТзТов, "products");
* Мы сначала определяем, какие колонки будут в `ТзТов` (`cis`, `product_cost`, `gtin`, `gtin_quantity`) в зависимости от `ПричВыб` и `СпосУч`. * Далее мы перебираем строки текущего документа (предполагается, что это документ 1С со списком маркированных товаров). * Для каждой строки создается новая строка в `ТзТов`. * Если причина выбытия `Развес`, то в `ТзТов.cis` записывается код маркировки (`Марка`). * Если причина выбытия иная, и используется **объемно-сортовой учет (ОСУ)** (`СпосУч = Перечисление.СпособУчета.ОСУ`), то мы извлекаем GTIN из полного кода маркировки и записываем его в `ТзТов.gtin`. Также для первой строки группы (или если нет группировки) записываем количество в `ТзТов.gtin_quantity`. * Если используется **поэкземплярный учет**, то в `ТзТов.cis` записывается код идентификации (КИЗ), возможно, после обработки функцией `ПолучитьКИЗ()`. * В конце `ТзТов` добавляется в `СзЗаг` под ключом "products", что формирует массив товаров в JSON-структуре.
После того как все данные собраны в `СзЗаг`, мы преобразуем их в строку JSON.
СтрДжейсон = Сервис.DecodeToUTF8(ЗначениеВJSON(СзЗаг));
Кодировка = "base64";
Стр = Кодировать(СтрДжейсон, Кодировка);
// Удаляем переносы строк, которые могут быть добавлены при кодировании
Стр = СтрЗаменить(Стр, Симв(10), "");
СтрДок64 = СтрЗаменить(Стр, Симв(13), "");
* `ЗначениеВJSON(СзЗаг)`: Преобразует `СписокЗначений` в строку JSON. * `Сервис.DecodeToUTF8()`: Убеждаемся, что строка JSON имеет правильную кодировку UTF-8. * `Кодировать()`: Кодируем полученную JSON-строку в формат Base64. Это требование API "Честного ЗНАКа". * Мы также удаляем символы переноса строк (`Симв(10)`, `Симв(13)`), которые могут появиться после Base64-кодирования и помешать правильной обработке.
Для отправки документа в ЧЗ требуется его электронная подпись.
Тхт = СоздатьОбъект("Текст");
Тхт.ДобавитьСтроку(СтрДжейсон);
ИсхФайл = ВремКат + "DocJSON.json";
Тхт.Записать(ИсхФайл);
ПодпФайл = ВремКат + "DocJSON.json.sig";
Серт = ПолучитьСертификатПоОтпечатку(СокрЛП(глПользователь.ЭЦП.Отпечаток));
ПодписатьФайл(ИсхФайл, Серт, ПодпФайл, 1);
СтрЭЦП = УбратьПереносыСтрок(ПодпФайл);
* Мы записываем исходную JSON-строку (до Base64-кодирования) во временный файл `DocJSON.json`. * Получаем сертификат ЭЦП пользователя по его отпечатку с помощью `ПолучитьСертификатПоОтпечатку()`. * Подписываем созданный файл `ИсхФайл` с помощью `ПодписатьФайл()`, результатом будет файл подписи `DocJSON.json.sig`. * Содержимое файла подписи также необходимо преобразовать в строку и очистить от переносов строк.
Теперь мы собираем все части запроса в единую JSON-структуру, которая будет отправлена на сервер ЧЗ.
сзJSONЗапрос = СоздатьОбъект("СписокЗначений");
сзJSONЗапрос.ДобавитьЗначение("MANUAL", "document_format");
сзJSONЗапрос.ДобавитьЗначение(СтрДок64, "product_document");
сзJSONЗапрос.ДобавитьЗначение(СокрЛП(СтрЭЦП), "signature");
Если Причвыб = Перечисление.ПричВыбМарки.Развес Тогда
сзJSONЗапрос.ДобавитьЗначение("LK_RECEIPT", "type");
Иначе
Если СпосУч = Перечисление.СпособУчета.Поэкземплярный Тогда
сзJSONЗапрос.ДобавитьЗначение("LK_RECEIPT", "type");
Иначе
сзJSONЗапрос.ДобавитьЗначение("LK_GTIN_RECEIPT","type");
КонецЕсли;
КонецЕсли;
* `document_format`: Указываем "MANUAL". * `product_document`: Сюда помещаем нашу Base64-кодированную JSON-строку с данными документа (`СтрДок64`). * `signature`: Сюда помещаем Base64-кодированную строку электронной подписи (`СтрЭЦП`). * `type`: Тип документа в системе "Честный ЗНАК". В зависимости от причины выбытия (`ПричВыб`) и способа учета (`СпосУч`) это может быть "LK_RECEIPT" (для поэкземплярного или фасовки) или "LK_GTIN_RECEIPT" (для объемно-сортового учета).
Финальный шаг — отправка запроса на сервер "Честного ЗНАКа".
сзЗаголовки = СоздатьОбъект("СписокЗначений");
сзЗаголовки.ДобавитьЗначение("application/json;charset=utf-8", "Content-Type");
сзЗаголовки.ДобавитьЗначение("no-cache", "cache-control");
сзЗаголовки.ДобавитьЗначение("application/json", "Accept");
СтрОшибка = "";
Url = АдресСервераМОТП + "/" + ПрефиксВерсии + "/lk/documents/create?pg=" + ВернутьГруппуТовара();
Ответ = глКарлик_ВыполнитьЗапрос("POST", Url, ТекТокен, сзЗаголовки, СтрОшибка, сзJSONЗапрос);
УинДокаЦРПТ = СокрЛП(Ответ);
Записать();
ДоступностьКнопкиОтправить();
Форма.кнОтправить.Доступность(0);
Если СокрЛП(Ответ) <> "" Тогда
Пока Вопрос("Ожидание проверки статуса документа(~10 сек)......."+РазделительСтрок + "ОК - Проверить статус, Отмена - Прервать", 1,10) <> 2 Цикл
ПроверитьСтатус();
Если СокрЛП(Статус) <> "" Тогда
Провести();
Прервать;
КонецЦикла;
КонецЦикла;
КонецЕсли;
* `сзЗаголовки`: Мы формируем список HTTP-заголовков, указывая тип содержимого (`Content-Type`), кеширование (`cache-control`) и ожидаемый тип ответа (`Accept`). * `Url`: Формируем полный URL для запроса. Обратите внимание на `.../lk/documents/create?pg=...`, где `pg` — это группа товаров (например, "milk"). * `глКарлик_ВыполнитьЗапрос()`: Это предполагаемая вспомогательная функция, которая выполняет POST-запрос к указанному URL с токеном, заголовками и телом запроса. Она возвращает ответ от сервера ЧЗ. * Полученный ответ (предположительно, УИН документа в ЧЗ) сохраняется в `УинДокаЦРПТ`. * Далее идет цикл проверки статуса документа в ЧЗ с помощью функции `ПроверитьСтатус()`, пока документ не будет обработан или пользователь не прервет ожидание. Это важный момент, так как обработка документов в ЧЗ может занимать некоторое время.
Мы уже касались этих понятий, но давайте рассмотрим их подробнее, так как они критически влияют на формирование запроса. * **Поэкземплярный учет:** При этом методе каждая единица товара отслеживается индивидуально по ее уникальному коду маркировки (КИЗ). В запросе API необходимо указывать именно эти индивидуальные коды (`cis`). Этот метод применяется для таких категорий, как одежда, обувь, табак. * **Объемно-сортовой учет (ОСУ):** Здесь движение товара фиксируется по общему количеству и характеристикам (GTIN, сорт, модель, цвет, размер), без указания индивидуального идентификатора каждого экземпляра. В запросе API достаточно передать GTIN товара и его количество (`gtin_quantity`). Для молочной продукции и упакованной воды предприятия HoReCa и государственные учреждения обязаны вести объемно-сортовой учет. С 1 марта 2025 года для упакованной воды завершается маркировка, а в 2025 году для молочной продукции, бутилированной воды, БАД и антисептиков вводится поэкземплярный учет. Важно следить за актуальными требованиями для вашей товарной группы. Как мы видели в примере кода, логика выбора `cis` или `gtin`/`gtin_quantity` зависит от переменной `СпосУч`, которая, вероятно, содержит тип учета (`Перечисление.СпособУчета.ОСУ` или иной).
* **Актуальность документации:** "Честный ЗНАК" постоянно обновляет свои API. Мы настоятельно рекомендуем регулярно проверять актуальную документацию в личном кабинете системы маркировки. * **Тестирование:** Всегда начинайте с тестирования на тестовом контуре "Честного ЗНАКа", прежде чем переходить на промышленный. * **Обработка ошибок:** В представленном примере есть базовая обработка ошибок (например, отсутствие токена). В реальных системах необходимо предусмотреть более robustную систему обработки ошибок, включая логирование и уведомления. * **Компоненты для ЭЦП:** Для работы с УКЭП и получения токена могут потребоваться дополнительные компоненты (например, на C# или Python), взаимодействующие с системными криптопровайдерами, такими как КриптоПро CSP. Мы надеемся, что этот подробный разбор поможет вам успешно реализовать программное списание кодов маркировки в вашей системе 1С. Совместная работа с API "Честного ЗНАКа" открывает широкие возможности для автоматизации и оптимизации бизнес-процессов!
← К списку