Как программно перезаполнить движения в документе «ОперацияБух» без дублирования записей?

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

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

Анализ проблемы: почему записи дублируются?

Рассмотрим ситуацию, с которой столкнулся автор темы. В его коде используется стандартный механизм работы с набором записей регистра бухгалтерии Хозрасчетный. Однако ключевая ошибка кроется в последовательности действий. Проанализируем типичный ошибочный алгоритм:

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

Выясним причину ошибки: Метод Прочитать() считывает из базы данных в оперативную память все существующие на данный момент проводки документа. Когда далее в коде вызывается метод Добавить(), новая строка встает в конец уже существующего списка. При вызове Записать(Истина) система действительно заменяет старые данные в базе на те, что находятся в памяти, но в памяти на этот момент уже лежат и «старые» прочитанные записи, и «новые» добавленные. При следующем выполнении этого же кода программа снова прочитает (старые + новые) и добавит еще одну порцию «новых».

Решение №1: Правильное использование набора записей

Чтобы полностью заменить старые проводки новыми, нам необходимо исключить старые данные из процесса обработки. Рассмотрим правильный порядок действий:

Проанализируем исправленный код. Если ваша задача — сделать так, чтобы документ содержал только данные из вашей таблицы значений (например, РеализацииСНоменклатурой), вызов метода Прочитать() должен быть удален. Рассмотрим, как это выглядит на практике:


// Получаем ссылку на документ
ОперацияБухОбъект = ОперацияБухСсылка.ПолучитьОбъект();

// Обращаемся к набору записей (движениям)
НаборПроводок = ОперацияБухОбъект.Движения.Хозрасчетный;

// ВАЖНО: Метод Прочитать() НЕ вызываем, если старые записи нам не нужны в памяти!

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

// Записываем набор с параметром Замещать = Истина
НаборПроводок.Записать(Истина);

При таком подходе при каждом запуске кода формируется новый, пустой в памяти набор записей. Мы наполняем его актуальными данными и записываем в базу. Флаг Истина в методе Записать() гарантирует, что всё, что было в базе по этому регистратору ранее, будет стерто перед фиксацией нового набора.

Решение №2: Оптимизация через метод Загрузить()

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

Метод Загрузить() полностью заменяет содержимое набора записей данными из таблицы значений. Подробнее об этом и других приемах можно узнать в справочнике методов Бухгалтерии предприятия. Это избавляет от необходимости использовать цикл Для Каждого.


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

Этот способ является наиболее производительным, так как заполнение коллекции происходит на уровне внутренних механизмов платформы 1С, а не в цикле встроенного языка.

Особенности документа «ОперацияБух»

Работая с документом ОперацияБух, важно учитывать несколько нюансов, специфичных для типовых конфигураций (например, «Бухгалтерия предприятия 3.0»):

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

Рекомендации по выполнению кода

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

Рассмотрим оптимальные варианты:

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


ОперацияОбъект = ОперацияБухСсылка.ПолучитьОбъект();
Набор = ОперацияОбъект.Движения.Хозрасчетный;

// ... код заполнения набора ...

Набор.Записать(Истина);

// Обновляем сумму в шапке документа для корректного отображения в списках
ОперацияОбъект.СуммаОперации = Набор.Итог("Сумма");
ОперацияОбъект.Записать();

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

← На главную