Робот, который вносит документы в 1С через OData: права, защита от дублей и проверка до записи
Задача
В рознице и на складе много данных рождается вне 1С. Продавцы считают товар на складе, менеджер сводит заказы в таблице, сайт принимает заявки. Потом кто-то переносит это в 1С руками: открывает документ, ищет каждую позицию в справочнике, вбивает количество. На сотне строк это час работы и почти гарантированные ошибки: перепутанная позиция с похожим названием, лишний ноль, пропущенная строка.
Хотелось, чтобы данные попадали в 1С сами, но при этом последнее слово оставалось за человеком. Бухгалтер не должен узнавать о документе тогда, когда он уже провёлся и поменял остатки. Поэтому правило сформулировали сразу: робот создаёт документ, проводит только бухгалтер.
Ниже схема на примере документа «Пересчёт товаров» в «Управлении торговлей 11», но тот же подход подходит для заказов, поступлений, перемещений и любых других документов, у которых есть стандартный интерфейс OData.
Схема
- Источник данных (форма на телефоне, таблица, сайт) складывает результат во внешнее хранилище.
- На сервере 1С по команде запускается скрипт. Он забирает результат, сопоставляет строки с объектами 1С и показывает, что именно собирается записать.
- После подтверждения скрипт создаёт документ через OData от имени пользователя-робота.
- Документ появляется в 1С непроведённым, с комментарием: откуда он, когда сформирован и что его нужно проверить.
- Бухгалтер открывает документ, смотрит и проводит.
Связь идёт изнутри наружу: сервер 1С сам забирает данные из внешнего хранилища. Снаружи к 1С никто не подключается, и интерфейс OData наружу не открывается вообще.
Шаг 1. Включить OData только для нужных объектов
Стандартный интерфейс OData есть у любой опубликованной на веб-сервере базы, но по умолчанию в нём нет ни одного объекта: корень отвечает пустым списком. Состав задаётся в самой базе, обработкой «Настройка автоматического REST-сервиса» или из Конфигуратора («Администрирование → Состав стандартного интерфейса OData»). Перезапуск сервера после изменения состава не нужен.
Открывать стоит только то, что нужно роботу. Для пересчёта это сам документ и объекты, на которые он ссылается: номенклатура, склады, единицы измерения, характеристики. Обработка сама предлагает добавить зависимые объекты, и для документа из типовой конфигурации их набирается несколько сотен. Это нормально: без зависимых объектов запись документа не пройдёт.
Проверить, что всё на месте, можно запросом метаданных:
GET /база/odata/standard.odata/$metadata
В ответе ищем EntityType с именем Document_ПересчетТоваров и его табличную часть. Хороший тон для робота: перед каждой записью сверять имена полей с $metadata этой базы и ничего не отправлять, если какого-то поля нет. Доработанные конфигурации отличаются от типовой, и лучше узнать об этом до записи.
Шаг 2. Отдельный пользователь-робот
Робот не должен работать под учётной записью администратора. Заводим отдельного пользователя и отдельный профиль групп доступа с минимальным набором ролей. Для пересчёта в «Управлении торговлей 11» хватило четырёх:
- базовые права БСП;
- базовые права УТ;
- добавление и изменение пересчётов товаров;
- удалённый доступ (OData).
Права на проведение у робота нет. Если в каком-то профиле оно появляется вместе с другой ролью, это нужно записать как известный зазор и проверять результат после каждой записи (об этом ниже). Входа в обычный клиент у робота тоже нет, только удалённый доступ.
Отдельный пользователь даёт ещё одно преимущество: в журнале регистрации сразу видно, какие документы создал робот, а какие люди.
Шаг 3. Закрыть OData от внешнего мира
Частая ситуация: база опубликована на веб-сервере, и публикация проброшена наружу, чтобы сотрудники могли зайти в веб-клиент из дома. Тогда вместе с веб-клиентом наружу смотрит и OData. Роботу внешний доступ не нужен, он работает на том же сервере, поэтому интерфейс закрываем на уровне веб-сервера.
Для Apache это делается файлом .htaccess в каталоге публикации или блоком в конфигурации, без перезапуска самого веб-сервера:
<If "%{REQUEST_URI} =~ m#/odata/#i">
Require local
</If>
<If "%{REQUEST_URI} =~ m#/(Post|Unpost)#i">
Require all denied
</If>
Первое правило пускает к OData только запросы с самого сервера. Второе запрещает проведение и отмену проведения через интерфейс для всех, на случай если кто-то когда-то выдаст роботу лишнюю роль. Двойная защита стоит пяти минут и снимает целый класс неприятностей.
Шаг 4. Сопоставление по коду, а не по названию
Самая трудоёмкая часть любого переноса: понять, какой строке во внешнем источнике соответствует какой элемент справочника. Сопоставление по названию ломается постоянно. В источнике «Евровагонка 2,5 м», в 1С «Вагонка 2,5м», а это один и тот же товар. И наоборот, две позиции с одинаковым началом названия различаются только сортом.
Надёжный ключ: внутренний идентификатор элемента справочника, в OData это поле Ref_Key. Источник данных должен с самого начала работать с этими идентификаторами. Самый простой путь взять их из обмена с сайтом: в выгрузке CommerceML у каждого товара есть Ид, это тот же GUID. Если обмена нет, справочник можно выгрузить через OData:
GET Catalog_Номенклатура?$format=json&$select=Ref_Key,Description,ЕдиницаИзмерения_Key,Parent_Key,IsFolder,DeletionMark
Перед записью скрипт проверяет, что каждый GUID действительно существует в базе. Проверяем батчами, чтобы не упереться в длину адреса:
GET Catalog_Номенклатура?$format=json&$select=Ref_Key,Description
&$filter=Ref_Key eq guid'…' or Ref_Key eq guid'…' or …
Если хотя бы одного товара в 1С нет, перенос останавливается и показывает список. Молча пропускать строки нельзя: бухгалтер увидит документ без части позиций и решит, что их не считали.
Отдельно сравниваем названия: если в источнике и в 1С по одному GUID разные названия, это не ошибка, но повод показать список человеку. Так находятся переименования и пересортица.
Шаг 5. Режим проверки до записи
У скрипта три режима, и пользоваться ими нужно в этом порядке:
python perenos.py --record 145 --check # только чтение: метаданные, склад, товары, дубли python perenos.py --record 145 --send --one # пробный документ с одной строкой python perenos.py --record 145 --send # документ целиком
В режиме проверки в 1С ничего не пишется. Скрипт читает метаданные, находит склад и все товары, проверяет, нет ли уже такого документа, и печатает итог: сколько строк, какой склад, какая дата, сумма количества. Этот итог удобно показать человеку до записи, одной строкой в мессенджере.
Пробный документ с одной строкой нужен при первом запуске на новой базе. Его создают, открывают в 1С, смотрят глазами и удаляют до настоящего переноса.
Шаг 6. Запись документа
Сама запись: один POST с шапкой и табличной частью.
POST Document_ПересчетТоваров?$format=json
Content-Type: application/json; charset=utf-8
{
"Date": "2026-01-15T18:30:00",
"Склад_Key": "…",
"Комментарий": "Из формы инвентаризации: счёт №145 от 15.01.2026 18:30. Создан роботом, НЕ проведён: проверить и провести вручную.",
"Товары": [
{"LineNumber": "1", "Номенклатура_Key": "…", "Количество": 12},
{"LineNumber": "2", "Номенклатура_Key": "…", "Количество": 3.5}
]
}
Несколько деталей, которые съедают время, если о них не знать:
- Ссылочные поля, которые в табличной части обязательны, но не нужны, заполняем пустым GUID
00000000-0000-0000-0000-000000000000. Иначе 1С ответит ошибкой о незаполненном поле. - Кириллица в адресе запроса должна быть закодирована, кроме служебных символов OData (
$, скобки, кавычки, двоеточие). - Таймаут ставим с запасом. На большом документе 1С думает дольше, чем кажется.
- Если строки с одним GUID встречаются в источнике несколько раз (например, позицию посчитали в двух местах склада), их нужно сложить до записи, а не отправлять двумя строками.
Шаг 7. Защита от дублей
Робот могут запустить дважды: по ошибке, после сбоя сети, при повторе команды. Документ при этом не должен появиться второй раз. Самое простое и надёжное решение: внешний идентификатор записи в комментарии документа и проверка перед каждой записью.
GET Document_ПересчетТоваров?$format=json&$select=Ref_Key,Number,Posted,DeletionMark
&$filter=substringof('счёт №145 ', Комментарий) and DeletionMark eq false
Если такой документ уже есть и не помечен на удаление, скрипт останавливается и показывает его номер и состояние. Пробел после номера в фильтре нужен, чтобы счёт №145 не совпал со счётом №1450.
Если у документа есть свободный строковый реквизит, идентификатор лучше хранить там: комментарий человек может отредактировать.
Шаг 8. Дата документа
Неочевидная, но важная вещь: какую дату ставить документу. Для пересчёта правильная дата: момент, когда закончили считать. 1С берёт учётные остатки на дату документа. Если между окончанием подсчёта и переносом прошли продажи, то при дате переноса они превратятся в ложные расхождения: в учёте товара уже меньше, а по факту посчитано больше.
С учётом этого общее правило такое: дата документа равна дате события во внешнем источнике, а время переноса пишем в комментарий.
Шаг 9. Проверка после записи
После POST скрипт сразу читает созданный документ обратно и проверяет три вещи:
- Документ не проведён: поле
Postedравноfalse. Если 1С вернулаtrue, это значит, что у робота оказалось право проведения, и с этим нужно разобраться до следующего запуска. - Количество строк совпадает с отправленным.
- Сумма количества совпадает с отправленной.
GET Document_ПересчетТоваров(guid'…')?$format=json
Результат пишется в журнал переноса: номер документа, ссылка, строки, сумма. Бухгалтеру уходит одна строка: какой документ создан и что его можно проверять.
Грабли
Лицензия. Сеанс OData занимает клиентскую лицензию. Если лицензий впритык, робот будет иногда получать отказ. Это надо знать до внедрения и посчитать лицензии.
Учётное количество в пересчёте. Колонка «По учёту» в документе, созданном через OData, остаётся пустой до проведения. При проведении 1С спрашивает, перезаполнить ли учётное количество во всех строках, и после согласия подставляет остатки на дату документа. Форма при этом не перерисовывается сама, документ нужно закрыть и открыть заново. Новых строк проведение не добавляет.
Команды заполнения. В документе пересчёта есть команды, которые бухгалтеру после робота трогать нельзя: «Фактическое количество по учётному» перезапишет посчитанное, «Заполнить по отбору» добавит строки, которых в подсчёте не было. Это стоит прописать в короткой памятке.
Характеристики и серии. Если товары учитываются с характеристиками, ключом строки становится пара: номенклатура и характеристика. Источник данных должен это поддерживать с самого начала.
Регистры только на чтение. Через OData удобно читать остатки (AccumulationRegister_ТоварыНаСкладах/Balance(...)), но записывать в регистры напрямую нельзя и не нужно: всё меняется только через документы.
Что в итоге
- Данные попадают в 1С без перепечатки, с проверкой каждой позиции по коду.
- Человек остаётся на последнем шаге: робот ничего не проводит и не удаляет.
- OData не смотрит в интернет, у робота свой пользователь с минимальными правами.
- Повторный запуск дубля не создаёт. Ошибка сопоставления останавливает перенос до записи.
- Конфигурацию дорабатывать не пришлось: всё сделано на стандартном интерфейсе и настройках прав.
Тот же каркас (проверка по метаданным, сопоставление по GUID, режим проверки, запись, контроль после записи) переносится на другие документы почти без изменений: меняются имя документа, табличная часть и правила дат.
Вступайте в нашу телеграмм-группу Инфостарт
Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.