Почему при проведении документа все проводки формируются по типу последней строки?

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

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

Такое поведение не является сбоем платформы. Это следствие некорректной структуры кода в модуле проведения. Давайте детально разберем причину на конкретном примере и посмотрим, как это исправить.

Анализ кода и причина некорректной работы

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

Вот фрагмент кода из модуля проведения документа:


// ... Код запроса к табличной части ...
Пока Выборка.Следующий() Цикл
    
    Если Выборка.НоменклатураТип = Перечисления.ТипыНоменклатуры.Товар Тогда 
        Движение = Движения.Товары.Добавить();
        // ... формирование движений по регистру Товары
    Иначе  
        Движение = Движения.Расходы.Добавить();
        // ... формирование движений по регистру Расходы
    КонецЕсли;
    
    Движение = Движения.ВзаиморасчетыСКонтрагентами.Добавить();
    // ... формирование движений по регистру Взаиморасчеты
КонецЦикла;
     
// !!! ОШИБКА НАХОДИТСЯ ЗДЕСЬ !!!
Движения.Управленческий.Записывать = Истина; 
Если Выборка.НоменклатураТип = Перечисления.ТипыНоменклатуры.Товар Тогда
    Для Каждого ТекСтрокаТоварыИУслуги Из ТоварыИУслуги Цикл
        Движение = Движения.Управленческий.Добавить();
        Движение.СчетДт = ПланыСчетов.Управленческий.Товары;
        // ... заполнение проводки для товара
    КонецЦикла;
КонецЕсли;

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

Выясним причину по шагам:

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

В результате мы и получаем наблюдаемый эффект: если последняя позиция в документе — товар, все проводки формируются по счету "Товары". Если последняя позиция — услуга, все проводки формируются по счету "Расходы", независимо от реального типа номенклатуры в каждой конкретной строке.

Исправление кода и правильная логика

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

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

Вот как будет выглядеть исправленный и оптимизированный код:


// ... Код запроса остается без изменений ...

// Включаем запись для всех нужных регистров перед циклом
Движения.Управленческий.Записывать = Истина; 
Движения.Товары.Записывать = Истина;
Движения.Расходы.Записывать = Истина;
Движения.ВзаиморасчетыСКонтрагентами.Записывать = Истина;

// Основной цикл по сгруппированным данным
Пока Выборка.Следующий() Цикл
    
    // --- Формируем движения по регистрам накопления ---
    Если Выборка.НоменклатураТип = Перечисления.ТипыНоменклатуры.Товар Тогда 
        ДвижениеТовар = Движения.Товары.ДобавитьПриход();
        ДвижениеТовар.Период = Дата;
        ДвижениеТовар.Номенклатура = Выборка.Номенклатура;
        ДвижениеТовар.Количество = Выборка.Количество;
        ДвижениеТовар.Сумма = Выборка.Сумма; 
    Иначе  
        ДвижениеРасход = Движения.Расходы.ДобавитьПриход(); // Услуги тоже "приходят" на затраты
        ДвижениеРасход.Период = Дата;
        ДвижениеРасход.Номенклатура = Выборка.Номенклатура;
        ДвижениеРасход.Сумма = Выборка.Сумма;
    КонецЕсли;
    
    // --- Формируем движения по регистру бухгалтерии "Управленческий" ---
    // Для каждой строки выборки создаем свою проводку
    ДвижениеУпр = Движения.Управленческий.Добавить();
    ДвижениеУпр.Период = Дата;
    // Общая часть проводки (кредит)
    ДвижениеУпр.СчетКт = ПланыСчетов.Управленческий.РасчетыСПоставщиками;
    ДвижениеУпр.СубконтоКт[ПланыВидовХарактеристик.ВидыСубконто.контрагенты] = Поставщик;
    ДвижениеУпр.Сумма = Выборка.Сумма;

    // Уникальная часть проводки (дебет) определяется типом номенклатуры
    Если Выборка.НоменклатураТип = Перечисления.ТипыНоменклатуры.Товар Тогда
        ДвижениеУпр.СчетДт = ПланыСчетов.Управленческий.Товары;
        ДвижениеУпр.КоличествоДт = Выборка.Количество;
        ДвижениеУпр.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконто.номенклатура] = Выборка.Номенклатура;
    Иначе // Это услуга
        ДвижениеУпр.СчетДт = ПланыСчетов.Управленческий.Расходы;
    КонецЕсли;

КонецЦикла;

// Движение по взаиморасчетам делаем ОДИН РАЗ после цикла на ОБЩУЮ сумму документа
Если ТоварыИУслуги.Количество() > 0 Тогда
    ДвижениеВзаиморасчеты = Движения.ВзаиморасчетыСКонтрагентами.ДобавитьРасход();
    ДвижениеВзаиморасчеты.Период = Дата;
    ДвижениеВзаиморасчеты.Контрагент = Поставщик;
    // Используем итоговое значение из табличной части документа
    ДвижениеВзаиморасчеты.Сумма = ТоварыИУслуги.Итог("Сумма");
КонецЕсли;

Что изменилось в исправленном коде:

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

← На главную