Почему задваиваются движения в регистре накопления при проведении документа в 1С и как это исправить?

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

При разработке алгоритмов проведения документов часто возникает задача: формировать движения по регистру накопления в зависимости от состояния реквизитов в табличной части. Например, если в строке заполнено только время начала работы, необходимо выполнить запись с видом движения Приход, а если заполнены и время начала, и время окончания — движение с видом Расход.

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

Выясним причину проблемы

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


Для Каждого ТекСтрока Из ТехнологическиеОперации Цикл 
    Если ЗначениеЗаполнено(ТекСтрока.ВремяНачала) И Не ЗначениеЗаполнено(ТекСтрока.ВремяОкончания) Тогда 
        Движения.ВыполнениеМаршрутныхКарт.ВыполнитьПриход();    
    ИначеЕсли ЗначениеЗаполнено(ТекСтрока.ВремяНачала) И ЗначениеЗаполнено(ТекСтрока.ВремяОкончания) Тогда
        Движения.ВыполнениеМаршрутныхКарт.ВыполнитьРасход();        
    КонецЕсли;
КонецЦикла;

Проанализируем, почему этот код работает некорректно при наличии более чем одной строки в табличной части:

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

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

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

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

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

Рассмотрим пример кода в процедуре ОбработкаПроведения:


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

КонецПроцедуры

Способ 2. Разделение данных и использование запросников

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

Рассмотрим, как это выглядит в запросе:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Док.Ссылка КАК МаршрутнаяКарта,
    |	Док.Дата КАК Период,
    |	ТЧ.НомерОперации КАК НомерОперации,
    |	ТЧ.Операция КАК ТехнологическаяОперация,
    |	1 КАК Количество,
    |	ВЫБОР
    |		КОГДА ЗНАЧЕНИЕЗАПОЛНЕНО(ТЧ.ВремяНачала) И НЕ ЗНАЧЕНИЕЗАПОЛНЕНО(ТЧ.ВремяОкончания)
    |			ТОГДА ЗНАЧЕНИЕ(Перечисление.ВидыДвиженийНакопления.Приход)
    |		КОГДА ЗНАЧЕНИЕЗАПОЛНЕНО(ТЧ.ВремяНачала) И ЗНАЧЕНИЕЗАПОЛНЕНО(ТЧ.ВремяОкончания)
    |			ТОГДА ЗНАЧЕНИЕ(Перечисление.ВидыДвиженийНакопления.Расход)
    |	КОНЕЦ КАК ВидДвижения
    |ИЗ
    |	Документ.МаршрутнаяКартаПроизводства.ТехнологическиеОперации КАК ТЧ
    |ГДЕ
    |	ТЧ.Ссылка = &Ссылка
    |	И ЗНАЧЕНИЕЗАПОЛНЕНО(ТЧ.ВремяНачала)";

Запрос.УстановитьПараметр("Ссылка", Ссылка);
ТаблицаДвижений = Запрос.Выполнить().Выгрузить();

// Загружаем полученную таблицу прямо в набор записей регистра
Движения.ВыполнениеМаршрутныхКарт.Загрузить(ТаблицаДвижений);
Движения.ВыполнениеМаршрутныхКарт.Записывать = Истина;

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

Важные рекомендации при работе с проведениями

← На главную