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