Как заполнить новый документ на основании данных регистра сведений по текущему регистратору

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки в среде 1С часто возникает задача переноса данных из одного документа в другой. Иногда для этого разработчики используют промежуточные объекты, например, подчиненные регистры сведений. Рассмотрим типичную ситуацию: у нас есть документ «Ведомость выдачи СИЗ», который при проведении делает записи в одноименный регистр сведений. Нам необходимо по нажатию кнопки «Отправить» прочитать эти движения и заполнить ими совершенно другой документ. Выясним, как правильно построить запрос к регистру сведений с отбором по регистратору, разберем частые логические ошибки и рассмотрим альтернативные, более производительные архитектурные подходы.

Решение проблемы: Фильтрация запроса по регистратору

Если мы делаем запрос к регистру сведений напрямую, система по умолчанию выбирает абсолютно все записи, хранящиеся в базе данных. Чтобы ограничить выборку только строками текущего документа, из которого была нажата кнопка, необходимо наложить жесткое условие на измерение или стандартный реквизит Регистратор.

Разберем по шагам, как должен выглядеть правильный запрос на стороне источника:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ВедомостьВыдачиСИЗ.Номенклатура КАК Номенклатура,
    |	ВедомостьВыдачиСИЗ.Сотрудник КАК Сотрудник,
    |	ВедомостьВыдачиСИЗ.Количество КАК Количество,
    |	ВедомостьВыдачиСИЗ.Регистратор КАК Регистратор
    |ИЗ
    |	РегистрСведений.ВедомостьВыдачиСИЗ КАК ВедомостьВыдачиСИЗ
    |ГДЕ
    |	ВедомостьВыдачиСИЗ.Количество <> 0
    |	И ВедомостьВыдачиСИЗ.Регистратор = &Регистратор";

// Передаем ссылку на текущий документ в качестве параметра запроса
Запрос.УстановитьПараметр("Регистратор", Объект.Ссылка);

РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();

Пока Выборка.Следующий() Цикл
    // Логика заполнения нового документа
    НовСтр = НовДок.Товары.Добавить();
    ЗаполнитьЗначенияСвойств(НовСтр, Выборка);
КонецЦикла;

Использование конструкции ВедомостьВыдачиСИЗ.Регистратор = &Регистратор гарантирует, что СУБД вернет только те записи регистра, которые были сформированы именно этим документом.

Важная ловушка: Работа с новым или измененным документом

При использовании вышеописанного метода разработчики регулярно сталкиваются с проблемой: если пользователь только что создал документ, заполнил его на форме и нажал кнопку «Отправить» еще до первого сохранения, то процедура вернет пустой результат. Выясним причину этого поведения.

Дело в том, что у нового (еще не записанного в базу данных) документа ссылка Объект.Ссылка является пустой (то есть имеет значение ДокументСсылка.ИмяДокумента.Пустая). Соответственно, никаких движений в регистре сведений физически существовать не может. СУБД делает запрос к таблице БД, где записей с пустой ссылкой-регистратором просто нет.

Для предотвращения этой ошибки перед выполнением запроса обязательно нужно предусмотреть проверку на модифицированность и наличие ссылки в базе данных. Проанализируем следующий защитный код:


// Проверяем, изменен ли документ или он вообще еще не записан в БД
Если ЭтотОбъект.Модифицированность() Или Объект.Ссылка.Пустая() Тогда
    // Записываем документ с проведением, чтобы гарантировать формирование движений в регистре
    Попытка
        ЭтотОбъект.Записать(РежимЗаписиДокумента.Проведение);
    Исключение
        ВызватьИсключение "Не удалось записать документ для отправки: " + ОписаниеОшибки();
    КонецПопытки;
КонецЕсли;

Только после успешной записи и проведения документа можно гарантированно обращаться к таблице регистра сведений через запрос.

Альтернативный вариант: Передача данных формы напрямую (без обращения к БД)

Обращение к базе данных через запрос к регистру сведений в контексте интерактивной работы с формой — это дополнительная нагрузка на сервер СУБД. Если данные уже выведены на экран пользователя в виде таблицы значений (ТЗ) или табличной части формы, мы можем получить их непосредственно из оперативной памяти сервера приложений, минуя повторное чтение из БД.

В управляемом приложении 1С для этого используется высокопроизводительный метод РеквизитФормыВЗначение. Посмотрим на пример получения данных динамической таблицы формы:


// Процедура выполняется на сервере формы
&НаСервере
Процедура ОтправитьДанныеНаСервере()
    
    // Преобразуем табличную часть или реквизит формы типа "ТаблицаФормы" обратно в прикладной объект
    ТаблицаДанных = РеквизитФормыВЗначение("Объект.Товары"); // или имя вашей динамической ТЗ на форме
    
    // Теперь у нас в памяти есть готовая ТаблицаЗначений, которую можно обработать
    Для Каждого СтрокаДанных Из ТаблицаДанных Цикл
        // Нам не нужно делать запросы к БД, данные уже у нас в руках!
        НовСтр = НовДок.Товары.Добавить();
        НовСтр.Номенклатура = СтрокаДанных.Номенклатура;
        НовСтр.Количество   = СтрокаДанных.Количество;
    КонецЦикла;
    
КонецПроцедуры

Этот способ работает значительно быстрее, экономит ресурсы сервера и избавляет от необходимости принудительно сохранять и проводить документ ради отправки данных.

Архитектурные рекомендации по интеграции и передаче данных

Если ваша задача состоит в передаче этой таблицы в другую (внешнюю) базу данных, использование физического регистра сведений в качестве «транзитного буфера» является серьезным архитектурным антипаттерном. База данных тратит огромные ресурсы на транзакционную запись, индексацию и последующее удаление временных строк.

Рассмотрим более изящные и стандартные способы передачи структур данных между информационными базами 1С:

  1. Сериализация в формат JSON. Это самый быстрый и современный кроссплатформенный стандарт. Мы можем сериализовать нашу таблицу значений в строку JSON на стороне источника и передать её через HTTP-сервис или Web-сервис в базу-приемник.
  2. Использование фабрики XDTO. Платформа 1С из коробки поддерживает сериализацию системного типа ValueTable (ТаблицаЗначений) через пространство имен http://v8.1c.ru/8.1/data/core. Это позволяет передавать таблицы без ручного разбора колонок.

Проанализируем пример простой сериализации таблицы значений в JSON для последующей отправки:


// Сериализация таблицы значений в JSON-строку
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();

// Записываем массив структур, чтобы сохранить типизацию колонок
МассивСтрок = Новый Массив;
Для Каждого СтрокаТЗ Из ТаблицаДанных Цикл
    СтруктураСтроки = Новый Структура;
    СтруктураСтроки.Вставить("НоменклатураКод", СтрокаТЗ.Номенклатура.Код); // Передаем коды или UUID для надежности
    СтруктураСтроки.Вставить("СотрудникФИО", СтрокаТЗ.Сотрудник.Наименование);
    СтруктураСтроки.Вставить("Количество", СтрокаТЗ.Количество);
    МассивСтрок.Добавить(СтруктураСтроки);
КонецЦикла;

ЗаписатьJSON(ЗаписьJSON, МассивСтрок);
СтрокаJSON = ЗаписьJSON.Закрыть();

// Полученную СтрокуJSON можно отправлять через HTTPСоединение в другую базу

Предотвращение транзакционных блокировок

Важное предупреждение: если отправка данных во внешнюю базу выполняется по COM-соединению или по сети (HTTP/SOAP), ни в коем случае не выполняйте этот код внутри транзакции проведения документа (например, непосредственно в модуле объекта в обработчике ОбработкаПроведения). Любая задержка сети или зависание внешней базы приведет к тому, что транзакция в исходной базе останется открытой. Это намертво заблокирует таблицы СУБД для всех остальных пользователей системы.

Вызывайте отправку строго вне транзакций — например, в обработчиках событий формы ПослеЗаписи на клиенте или с помощью асинхронного вызова фоновых заданий.

← На главную