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

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

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

Анализ типичных ошибок при переносе кода в общий модуль

Проанализируем фрагмент кода, который чаще всего приводит к потере движений:


// Ошибочный подход внутри общего модуля
НаборЗаписей = ДокументОбъект.ПолучитьОбъект().Движения.ск_ДвиженияПоДоговору;
НаборЗаписей.Отбор.Регистратор.Установить(ДокументОбъект);
НаборЗаписей.Записывать = Истина;

Для Каждого Строка Из ТаблицаРисков Цикл
    // Заполнение движений...
КонецЦикла;

НаборЗаписей.Записать();

В приведенном коде скрыто сразу несколько критических архитектурных и платформенных ошибок:

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

Решение: Передача коллекции движений в общий модуль

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

В модуле объекта документа в процедуре ОбработкаПроведения вызываем экспортную процедуру общего модуля:


Процедура ОбработкаПроведения(Отказ, РежимПроведения)
    
    // Передаем сам объект документа и коллекцию его движений
    ск_ОбщийМодуль.СформироватьДвиженияПоДоговору(ЭтотОбъект, Движения.ск_ДвиженияПоДоговору);
    
КонецПроцедуры

В общем модуле реализуем формирование записей и взведение флага записи:


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

Обратите внимание: метод Записать() вызывать не нужно. Платформа самостоятельно оптимизирует запись всех наборов с установленным свойством Записывать = Истина в конце транзакции проведения.

Оптимальное решение: Пакетное заполнение движений через запрос

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


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

Формирование движений вне контекста проведения документа

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


НаборЗаписей = РегистрыНакопления.ск_ДвиженияПоДоговору.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);

// Заполнение набора...
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период = ТекущаяДата();
НоваяЗапись.ВидДвижения = ВидДвиженияНакопления.Приход;
НоваяЗапись.Сумма = 1000;

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

Важно помнить:

  1. В процедуре ОбработкаПроведения всегда работайте с коллекцией Движения текущего объекта.
  2. Никогда не получайте Ссылка.ПолучитьОбъект() внутри проведения этого же документа.
  3. Устанавливайте свойство Записывать = Истина для нужных регистров вместо ручного вызова метода Записать().
← На главную