Почему при последовательном вызове ВыполнитьПриход() и ВыполнитьРасход() портятся движения регистра в 1С и как это исправить?

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

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

Рассмотрим подробно причины такого поведения экспортных методов и разберем проверенные способы правильного формирования движений прихода и расхода.

Выясним причины возникновения проблемы

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


ТаблицаПриходов = Запрос.Выполнить().Выгрузить();    
НаборДвижений = Движения.НакопленныеБонусы;
ТаблицаДвижений = НаборДвижений.Выгрузить();
ОбщегоНазначения.ЗагрузитьВТаблицуЗначений(ТаблицаПриходов, ТаблицаДвижений);

НаборДвижений.мПериод = Дата;
НаборДвижений.мТаблицаДвижений = ТаблицаДвижений;    
НаборДвижений.ВыполнитьПриход();

// Далее очищается ТаблицаДвижений и вызывается НаборДвижений.ВыполнитьРасход()

Главные причины, по которым при последующем вызове ВыполнитьРасход() движения формируются некорректно:

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

Способ 1. Прямой обход результатов запроса и заполнение движений (Рекомендуемый)

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

Посмотрим на пример реализации данного подхода:


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

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

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

Способ 2. Формирование единой таблицы с указанием колонки ВидДвижения

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

Разберем данный вариант по шагам:

  1. Сформируем общую таблицу значений, содержащую поля для всех строк.
  2. Для приходных строк явно установим значение колонки ВидДвижения = ВидДвиженияНакопления.Приход.
  3. Для расходных строк явно установим ВидДвижения = ВидДвиженияНакопления.Расход.
  4. Передадим объединенную таблицу в переменные набора и вызовем единую процедуру.

// Подготовка единой таблицы движений
ТаблицаДвижений = Запрос.Выполнить().Выгрузить(); 
// В запросе колонка ВидДвижения должна быть заранее заполнена 
// нужными значениями ВидДвиженияНакопления.Приход / Расход

НаборДвижений = Движения.НакопленныеБонусы;
НаборДвижений.мПериод = Дата;
НаборДвижений.мТаблицаДвижений = ТаблицаДвижений;

// Вызываем универсальный метод проведения
НаборДвижений.ВыполнитьДвижения();

Способ 3. Очистка набора записей перед повторным вызовом

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

Проанализируем следующий код:


// 1. Формируем приход
НаборДвижений = Движения.НакопленныеБонусы;
НаборДвижений.мПериод = Дата;
НаборДвижений.мТаблицаДвижений = ТаблицаПриходов;
НаборДвижений.ВыполнитьПриход();

// 2. Очищаем набор записей платформы и промежуточную таблицу
ТаблицаРасходов.Колонки.Удалить("ВидДвижения"); // Удаляем колонку, чтобы избежать конфликта значений

НаборДвижений.мТаблицаДвижений = ТаблицаРасходов;
НаборДвижений.ВыполнитьРасход();

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

← На главную