Как получить движения документа по регистру сведений в процедуре обработки проведения?

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

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

Разберем механизм транзакционной видимости данных

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

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

Решение 1: Явная запись набора записей

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

Посмотрим на пример кода:


// Формируем движения обычным способом
НоваяЗапись = Движения.РегистрСведенийЦены.Добавить();
НоваяЗапись.Период = ТекущаяДата();
НоваяЗапись.Номенклатура = СсылкаНаТовар;
НоваяЗапись.Цена = 100;

// Явно записываем движения в базу данных (внутри транзакции)
Движения.РегистрСведенийЦены.Записать();

// Теперь данные доступны для запроса
Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	РегистрСведенийЦены.Цена КАК Цена
    |ИЗ
    |	РегистрСведений.РегистрСведенийЦены КАК РегистрСведенийЦены
    |ГДЕ
    |	РегистрСведенийЦены.Регистратор = &Регистратор";

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

Важные нюансы этого метода:

  1. Видимость: Эти данные будут видны только внутри текущей транзакции. Другие сеансы пользователей их не увидят до полного завершения проведения.
  2. Производительность: Частые вызовы Записать() могут замедлить работу системы, так как провоцируют дополнительные обращения к СУБД.
  3. Режимы записи: Обратите внимание на свойство «Запись движений» в настройках документа. Если там установлено «Записывать выбранные», то после вашего вызова Записать() платформа может повторно перезаписать эти данные в конце процедуры, если вы не сбросите флаг модифицированности.

Решение 2: Использование временных таблиц (рекомендуемый способ)

Проанализируем более профессиональный и эффективный подход. Зачем записывать данные в базу, если они уже есть у нас в оперативной памяти в коллекции Движения? Мы можем выгрузить их в таблицу значений и передать в запрос как временную таблицу. Это работает быстрее и не создает лишней нагрузки на транзакционный лог СУБД.

Рассмотрим алгоритм реализации по шагам:

  1. Выгружаем сформированные движения в объект ТаблицаЗначений.
  2. Создаем МенеджерВременныхТаблиц.
  3. Помещаем таблицу значений в запрос и создаем из нее временную таблицу.
  4. Используем эту таблицу в основном запросе, соединяя ее с другими данными при необходимости.

Пример программной реализации:


// 1. Выгружаем движения в ТЗ
ТаблицаДвижений = Движения.РегистрСведенийЦены.Выгрузить();

// 2. Инициализируем запрос и менеджер временных таблиц
Запрос = Новый Запрос;
МВТ = Новый МенеджерВременныхТаблиц;
Запрос.МенеджерВременныхТаблиц = МВТ;

// 3. Помещаем данные во временную таблицу
Запрос.Текст = 
    "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Движения ИЗ &ТЗ КАК ТЗ";
Запрос.УстановитьПараметр("ТЗ", ТаблицаДвижений);
Запрос.Выполнить();

// 4. Теперь мы можем работать с этими данными в любом запросе
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ВТ_Движения.Номенклатура,
    |	ВТ_Движения.Цена
    |ИЗ
    |	ВТ_Движения КАК ВТ_Движения";

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

Этот метод считается «чище», так как он не зависит от физического состояния таблиц БД и гарантирует, что вы работаете именно с тем набором данных, который актуален для текущего объекта в данный момент времени.

Проблема «старых» записей при перепроведении

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

Если вы воспользуетесь первым методом (запросом к физической таблице) и забудете вызвать Записать(), вы получите данные, которые были актуальны до текущего изменения. Платформа 1С очищает старые движения автоматически, но делает это часто одновременно с записью новых в конце транзакции. Поэтому использование МенеджерВременныхТаблиц или принудительная очистка набора перед запросом является критически важным условием для корректных расчетов.

Особенности независимых регистров сведений

Стоит упомянуть ситуацию, когда регистр сведений является независимым. В этом случае документ вообще не содержит его в своей коллекции Движения. Работа с такими регистрами из модуля документа возможна только через создание менеджера набора записей вручную:


Набор = РегистрыСведений.МойНезависимыйРегистр.СоздатьНаборЗаписей();
Набор.Отбор.Регистратор.Установить(ЭтотОбъект.Ссылка); // Если есть такой реквизит
// ... заполнение ...
Набор.Записать(); 

Здесь запрос к регистру станет возможен только после вызова Набор.Записать(). Без этого действия записи физически не будут существовать нигде, кроме оперативной памяти вашего объекта.

Резюме и рекомендации

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

← На главную