При автоматизации бухгалтерских операций в 1С:Предприятии программисты часто сталкиваются с задачей программного формирования или корректировки проводок в документе ОперацияБух (или Операция). Одной из самых распространенных проблем является «размножение» записей: при каждом повторном запуске алгоритма количество строк в документе увеличивается, хотя логика подразумевает их полную замену. В этой статье мы подробно разберем, почему это происходит и как правильно организовать процесс обновления набора записей.
Рассмотрим ситуацию, с которой столкнулся автор темы. В его коде используется стандартный механизм работы с набором записей регистра бухгалтерии Хозрасчетный. Однако ключевая ошибка кроется в последовательности действий. Проанализируем типичный ошибочный алгоритм:
Прочитать().Добавить().Записать(Истина).Выясним причину ошибки: Метод Прочитать() считывает из базы данных в оперативную память все существующие на данный момент проводки документа. Когда далее в коде вызывается метод Добавить(), новая строка встает в конец уже существующего списка. При вызове Записать(Истина) система действительно заменяет старые данные в базе на те, что находятся в памяти, но в памяти на этот момент уже лежат и «старые» прочитанные записи, и «новые» добавленные. При следующем выполнении этого же кода программа снова прочитает (старые + новые) и добавит еще одну порцию «новых».
Чтобы полностью заменить старые проводки новыми, нам необходимо исключить старые данные из процесса обработки. Рассмотрим правильный порядок действий:
Проанализируем исправленный код. Если ваша задача — сделать так, чтобы документ содержал только данные из вашей таблицы значений (например, РеализацииСНоменклатурой), вызов метода Прочитать() должен быть удален. Рассмотрим, как это выглядит на практике:
// Получаем ссылку на документ
ОперацияБухОбъект = ОперацияБухСсылка.ПолучитьОбъект();
// Обращаемся к набору записей (движениям)
НаборПроводок = ОперацияБухОбъект.Движения.Хозрасчетный;
// ВАЖНО: Метод Прочитать() НЕ вызываем, если старые записи нам не нужны в памяти!
Для Каждого ТекущаяСтрока Из РеализацииСНоменклатурой Цикл
НоваяПроводка = НаборПроводок.Добавить();
НоваяПроводка.Активность = Истина;
НоваяПроводка.Период = ОперацияБухОбъект.Дата;
НоваяПроводка.Организация = ОперацияБухОбъект.Организация;
// Заполнение дебета
НоваяПроводка.СчетДт = ПланыСчетов.Хозрасчетный.ДоходыПолученныеВСчетБудущихПериодов;
НоваяПроводка.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Контрагенты] = ТекущаяСтрока.Контрагент;
НоваяПроводка.СубконтоДт[ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Договоры] = ТекущаяСтрока.Договор;
// Заполнение кредита
НоваяПроводка.СчетКт = ПланыСчетов.Хозрасчетный.ВыручкаНеЕНВД;
НоваяПроводка.СубконтоКт[ПланыВидовХарактеристик.ВидыСубконтоХозрасчетные.Номенклатура] = ТекущаяСтрока.Номенклатура;
НоваяПроводка.Сумма = ТекущаяСтрока.Сумма;
КонецЦикла;
// Записываем набор с параметром Замещать = Истина
НаборПроводок.Записать(Истина);
При таком подходе при каждом запуске кода формируется новый, пустой в памяти набор записей. Мы наполняем его актуальными данными и записываем в базу. Флаг Истина в методе Записать() гарантирует, что всё, что было в базе по этому регистратору ранее, будет стерто перед фиксацией нового набора.
Если структура вашей таблицы значений совпадает с именами реквизитов регистра (например, колонки называются СчетДт, СчетКт, Сумма и т.д.), можно значительно ускорить код и сделать его чище. Разберем по шагам использование метода Загрузить().
Метод Загрузить() полностью заменяет содержимое набора записей данными из таблицы значений. Подробнее об этом и других приемах можно узнать в справочнике методов Бухгалтерии предприятия. Это избавляет от необходимости использовать цикл Для Каждого.
НаборПроводок = Движения.Хозрасчетный;
// ТаблицаЗначенийДвижений должна иметь колонки, совпадающие с реквизитами регистра
НаборПроводок.Загрузить(ТаблицаЗначенийДвижений);
НаборПроводок.Записать(Истина);
Этот способ является наиболее производительным, так как заполнение коллекции происходит на уровне внутренних механизмов платформы 1С, а не в цикле встроенного языка.
Работая с документом ОперацияБух, важно учитывать несколько нюансов, специфичных для типовых конфигураций (например, «Бухгалтерия предприятия 3.0»):
ОперацияБух есть реквизит СуммаОперации. В отличие от обычных документов, проводки которых формируются автоматически при проведении, здесь сумма документа в списке часто берется именно из этого реквизита объекта. После программного изменения проводок обязательно нужно пересчитать итоговую сумму и записать ее в сам документ.РучнаяКорректировка (удобно через настройка схем калькуляции и корректировки проводок). Для ОперацияБух движения по умолчанию считаются «ручными». Если вы программно меняете движения в других документах, например, внедряете механизм ручной корректировки проводок больничного листа, вам нужно установить флаг РучнаяКорректировка = Истина, иначе при следующем перепроведении документа ваши правки будут удалены стандартным механизмом проведения.Активность. Если установить Ложь, проводки будут видны в документе, но не будут участвовать в формировании итогов и не попадут в отчеты (ОСВ и др.).Проанализируем ситуацию: где лучше размещать этот код? Как справедливо заметили участники форума, если разместить такой код в модуле объекта документа в обработке проведения, можно войти в бесконечную рекурсию или получить конфликты блокировок.
Рассмотрим оптимальные варианты:
Посмотрим на пример обновления реквизита СуммаОперации документа после изменения проводок:
ОперацияОбъект = ОперацияБухСсылка.ПолучитьОбъект();
Набор = ОперацияОбъект.Движения.Хозрасчетный;
// ... код заполнения набора ...
Набор.Записать(Истина);
// Обновляем сумму в шапке документа для корректного отображения в списках
ОперацияОбъект.СуммаОперации = Набор.Итог("Сумма");
ОперацияОбъект.Записать();
Таким образом, мы обеспечиваем целостность данных: и проводки в регистре, и отображаемая сумма в журнале документов будут соответствовать друг другу. Помните, что использование Прочитать() необходимо только тогда, когда вам нужно добавить записи к уже существующим (например, внедрить одну специфическую проводку, не трогая остальные), либо когда вам нужно проанализировать текущие движения перед их изменением.