Перенос алгоритмов проведения документов из модулей объектов в общие модули — распространенная практика рефакторинга в платформе 1С:Предприятие. Однако при попытке сформировать движения регистра накопления разработчики нередко сталкиваются с ситуацией, когда код выполняется без явных синтаксических ошибок, но записи в регистре не появляются либо перезаписываются пустым набором. Разберем по шагам причины такого поведения платформы и рассмотрим правильные варианты реализации логики проведения.
Проанализируем фрагмент кода, который чаще всего приводит к потере движений:
// Ошибочный подход внутри общего модуля
НаборЗаписей = ДокументОбъект.ПолучитьОбъект().Движения.ск_ДвиженияПоДоговору;
НаборЗаписей.Отбор.Регистратор.Установить(ДокументОбъект);
НаборЗаписей.Записывать = Истина;
Для Каждого Строка Из ТаблицаРисков Цикл
// Заполнение движений...
КонецЦикла;
НаборЗаписей.Записать();
В приведенном коде скрыто сразу несколько критических архитектурных и платформенных ошибок:
ОбработкаПроведения документ уже заблокирован и открыт в текущей транзакции записи. Вызов Ссылка.ПолучитьОбъект() создает в оперативной памяти второй, независимый экземпляр того же документа. Когда в общем модуле вызывается явная запись набора НаборЗаписей.Записать(), движения могут записаться в базу, однако при штатном завершении процедуры проведения исходный экземпляр документа выполняет запись собственных движений. Если в исходном объекте движений не было, платформа просто перезапишет созданные записи пустым набором.ПолучитьОбъект() доступен только у ссылочных типов (ДокументСсылка). У объекта ДокументОбъект такого метода нет. Кроме того, в метод Установить() для отбора регистратора необходимо передавать ссылку, а не объект.Движения документа отбор по регистратору уже жестко зафиксирован платформой, вызывать Отбор.Регистратор.Установить(...) и заполнять вручную поле НоваяЗапись.Регистратор не требуется.Рассмотрим основной и самый надежный способ выноса логики проведения в общий модуль. Вместо передачи ссылки или создания дублирующих объектов передадим коллекцию Движения документа непосредственно в процедуру общего модуля.
В модуле объекта документа в процедуре ОбработкаПроведения вызываем экспортную процедуру общего модуля:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Передаем сам объект документа и коллекцию его движений
ск_ОбщийМодуль.СформироватьДвиженияПоДоговору(ЭтотОбъект, Движения.ск_ДвиженияПоДоговору);
КонецПроцедуры
В общем модуле реализуем формирование записей и взведение флага записи:
Процедура СформироватьДвиженияПоДоговору(ДокументОбъект, ДвиженияПоДоговору) Экспорт
// Взводим флаг для автоматической записи платформой по завершении транзакции
ДвиженияПоДоговору.Записывать = Истина;
Для Каждого Строка Из ДокументОбъект.ТаблицаРисков Цикл
Если Строка.Сумма = 0 Тогда
Продолжить;
КонецЕсли;
НоваяЗапись = ДвиженияПоДоговору.Добавить();
Если Строка.Сумма > 0 Тогда
НоваяЗапись.ВидДвижения = ВидДвиженияНакопления.Приход;
НоваяЗапись.Сумма = Строка.Сумма;
Иначе
НоваяЗапись.ВидДвижения = ВидДвиженияНакопления.Расход;
НоваяЗапись.Сумма = -Строка.Сумма;
КонецЕсли;
НоваяЗапись.Период = ДокументОбъект.Дата;
НоваяЗапись.Контрагент = ДокументОбъект.Контрагент;
НоваяЗапись.Валюта = ДокументОбъект.ВалютаДокумента;
НоваяЗапись.Программа = Строка.Риск;
НоваяЗапись.Договор = ДокументОбъект.Договор;
КонецЦикла;
// Вызывать ДвиженияПоДоговору.Записать() здесь НЕ нужно!
КонецПроцедуры
Обратите внимание: метод Записать() вызывать не нужно. Платформа самостоятельно оптимизирует запись всех наборов с установленным свойством Записывать = Истина в конце транзакции проведения.
Если требуется высокая производительность при проведении документов с большими табличными частями, вместо построчного обхода в цикле Для Каждого используем метод Загрузить(). Посмотрим на пример:
Процедура СформироватьДвиженияОптимизировано(ДокументОбъект, ДвиженияПоДоговору) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| &Период КАК Период,
| &Контрагент КАК Контрагент,
| &Валюта КАК Валюта,
| &Договор КАК Договор,
| Таблица.Риск КАК Программа,
| ВЫБОР
| КОГДА Таблица.Сумма > 0 ТОГДА ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход)
| ИНАЧЕ ЗНАЧЕНИЕ(ВидДвиженияНакопления.Расход)
| КОНЕЦ КАК ВидДвижения,
| ВЫБОР
| КОГДА Таблица.Сумма > 0 ТОГДА Таблица.Сумма
| ИНАЧЕ -Таблица.Сумма
| КОНЕЦ КАК Сумма
|ИЗ
| &ТаблицаРисков КАК Таблица
|ГДЕ
| Таблица.Сумма <> 0";
Запрос.УстановитьПараметр("Период", ДокументОбъект.Дата);
Запрос.УстановитьПараметр("Контрагент", ДокументОбъект.Контрагент);
Запрос.УстановитьПараметр("Валюта", ДокументОбъект.ВалютаДокумента);
Запрос.УстановитьПараметр("Договор", ДокументОбъект.Договор);
Запрос.УстановитьПараметр("ТаблицаРисков", ДокументОбъект.ТаблицаРисков.Выгрузить());
ТаблицаДвижений = Запрос.Выполнить().Выгрузить();
// Загружаем сразу всю таблицу и взводим флаг записи
ДвиженияПоДоговору.Загрузить(ТаблицаДвижений);
ДвиженияПоДоговору.Записывать = Истина;
КонецПроцедуры
Если вам требуется сформировать или скорректировать движения регистра накопления вне процедуры проведения (например, во внешней обработке, регламентном или фоновом задании), используется создание независимого набора записей через менеджер регистра:
НаборЗаписей = РегистрыНакопления.ск_ДвиженияПоДоговору.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);
// Заполнение набора...
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период = ТекущаяДата();
НоваяЗапись.ВидДвижения = ВидДвиженияНакопления.Приход;
НоваяЗапись.Сумма = 1000;
// При прямой работе с набором явный вызов метода Записать обязателен
НаборЗаписей.Записать();
Важно помнить:
ОбработкаПроведения всегда работайте с коллекцией Движения текущего объекта.Ссылка.ПолучитьОбъект() внутри проведения этого же документа.Записывать = Истина для нужных регистров вместо ручного вызова метода Записать().