При сопровождении учетных систем на платформе «1С:Предприятие» нередко возникает задача групповой корректировки или отмены движений за прошлые периоды. Типовой механизм сторнирования в конфигурациях, использующих документ ОперацияБух, обычно ориентирован на интерактивную работу с одним документом-основанием непосредственно в форме. Когда возникает необходимость автоматизировать ежемесячное сторнирование пачки из десятков или сотен документов, разработчик сталкивается с выбором архитектуры чтения и записи данных.
Типичная прикладная ситуация: в информационной базе присутствует нетиповой документ, формирующий записи только в два регистра — регистр бухгалтерии (например, Хозрасчетный) и один регистр накопления (например, по учету НДС). Методологическое требование состоит в создании отдельного документа ОперацияБух на каждый сторнируемый документ (соотношение строго 1:1) для сохранения прозрачности учета. Основная сложность заключается в определении наиболее эффективного способа считывания исходных движений и формирования реверсивных записей.
При проектировании пакетных операций разработчики часто концентрируются на оптимизации алгоритмов чтения данных в оперативной памяти, упуская из виду реальные ресурсоемкие операции. На практике процесс сторнирования состоит из двух принципиально разных фаз:
Регистратор.ОперацияБух, запись их табличных частей и запись наборов записей регистров. Именно вызов метода Записать() для наборов записей регистров бухгалтерии и накопления является ключевым потребителем времени. В этот момент СУБД обновляет таблицы итогов, пересчитывает агрегаты, накладывает транзакционные блокировки и сбрасывает данные в журнал транзакций.Таким образом, оптимизация структуры циклов обхода данных в оперативной памяти дает пренебрежимо малый эффект по сравнению с правильной организацией транзакций и записи движений.
Распространенным заблуждением является стремление объединить обработку движений разнородных регистров в «один цикл» во избежание мнимой потери производительности. Если алгоритм выполняет последовательный обход выборки регистра бухгалтерии, а затем выборки регистра накопления по одному и тому же набору документов, суммарная алгоритмическая сложность остается строго линейной: O(N) + O(N) = O(N), где N — количество обрабатываемых строк.
Попытка объединить чтение таблицы регистра бухгалтерии (содержащей субконто, счета дебета и кредита, валютные суммы) и регистра накопления (с совершенно иным набором измерений и ресурсов) в единый запрос через искусственные соединения или объединения приводит к отрицательному результату. В результирующем наборе появляется множество пустых полей (значений NULL), увеличивается объем сетевого трафика между сервером 1С и СУБД, а код на встроенном языке становится перегруженным условными проверками вида записи. Раздельное чтение структурно несвязанных регистров является естественным и правильным подходом.
Другое частое заблуждение — полный отказ от штатного метода РезультатЗапроса.Выгрузить() в пользу ручного создания пустой таблицы значений и построчного добавления строк в цикле. Метод Выгрузить() реализован на уровне платформы на компилируемом коде ядра и переносит данные в оперативную память значительно быстрее, чем интерпретируемый платформой цикл со строковыми вызовами Добавить() и заполнением свойств. Исключение составляют лишь сценарии обработки сверхбольших объемов данных (сотни тысяч строк), когда единовременная выгрузка всей таблицы может вызвать исчерпание оперативной памяти рабочего процесса (ошибку Out of Memory). Для пакетов в 50–100 документов этот фактор не актуален.
Данный вариант повторяет логику интерактивной работы типового документа: в цикле по списку сторнируемых документов для каждого объекта создается отдельный НаборЗаписей по каждому регистру, устанавливается отбор по регистратору, вызывается метод Прочитать(), после чего инвертируются числовые ресурсы (сумма, количество) и данные переносятся в новый документ ОперацияБух.
Преимущества:
Ограничения:
Для минимизации обращений к базе данных список сторнируемых ссылок передается в единый пакетный запрос в качестве параметра или через временную таблицу. Пакет формирует две независимые таблицы движений: одну по регистру бухгалтерии, вторую — по регистру накопления. Для удобства сопоставления результатов на уровне запроса накладывается группировка или упорядочивание по полю Регистратор.
Далее обработка выполняется следующим образом:
Выгрузить() данный подход полностью их соблюдает, так как работает через потоковые выборки результата запроса без сохранения промежуточных таблиц значений в памяти.ОперацияБух выполняется в отдельной транзакции во избежание захвата длительных блокировок базы данных.Если массовое сторнирование даже 50–100 документов начинает вызывать задержки в интерфейсе пользователей или конфликты блокировок с оперативной работой бухгалтерии, интерактивное выполнение обработки целесообразно заменить на отложенное фоновое выполнение.
Схема включает в себя:
При выборе конкретного метода следует руководствоваться практическими условиями эксплуатации: