Как записать движения в оборотный регистр накопления из внешней обработки в 1С?

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

При разработке внешних обработок в среде «1С:Предприятие» программисты часто сталкиваются с задачей корректировки или добавления движений в регистры накопления без полного перепроведения документов-регистраторов. Типичная сложность, которая возникает при этом, — невозможность установить отбор в наборе записей регистра по произвольным полям (измерениям), кроме поля Регистратор.

В этой статье мы подробно разберем, почему существует это ограничение, рассмотрим по шагам классические и оптимизированные способы решения данной задачи, а также изучим стандарты разработки «1С» для быстрой и безопасной работы с базой данных.

Почему нельзя установить отбор по измерениям в регистре накопления?

Регистры накопления (как оборотные, так и регистры остатков) по своей архитектуре жестко подчинены регистратору. Платформа «1С:Предприятие» на уровне СУБД оптимизирует работу с движениями именно в разрезе документов, которые их сформировали. По этой причине объект РегистрНакопленияНаборЗаписей поддерживает установку отбора только по регистратору.

Если вам требуется изменить записи только по конкретному складу или номенклатуре, установить прямой отбор по этим измерениям в наборе записей не получится. Нам придется использовать обходные пути: считывать весь набор записей по документу, производить фильтрацию и модификацию данных в оперативной памяти, а затем записывать обновленный набор обратно в базу данных.

Способ №1. Модификация записей «на лету» в цикле (Рекомендуемый метод)

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

Разберем этот процесс по шагам на примере следующего кода:


// 1. Создаем объект набора записей для нужного регистра
НаборЗаписей = РегистрыНакопления.ИмяРегистра.СоздатьНаборЗаписей();

// 2. Устанавливаем обязательный отбор по документу-регистратору
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);

// 3. Считываем существующие движения из базы данных в оперативную память
НаборЗаписей.Прочитать();

// 4. Проходим по всем записям набора и модифицируем нужные строки по условию
Для Каждого Движение Из НаборЗаписей Цикл
    // Эмулируем дополнительный отбор по измерениям прямо в коде
    Если Движение.Номенклатура = ИскомыйТовар И Движение.Склад = ИскомыйСклад Тогда
        // Корректируем ресурс или реквизит движения
        Движение.Количество = НовоеЗначениеКоличества;
        Движение.Сумма = НовоеЗначениеСуммы;
    КонецЕсли;
КонецЦикла;

// 5. Отключаем выполнение тяжелых прикладных проверок для ускорения записи
НаборЗаписей.ОбменДанными.Загрузка = Истина;

// 6. Записываем измененный набор обратно в базу данных с замещением
НаборЗаписей.Записать(Истина);

Этот метод хорош тем, что мы не тратим ресурсы сервера на создание копии таблицы значений и обратно. Все изменения происходят непосредственно с элементами коллекции набора записей.

Способ №2. Использование промежуточной Таблицы Значений (Выгрузить / Загрузить)

Этот способ традиционно популярен на форумах разработчиков 1С. Его суть заключается в том, что существующие движения выгружаются в объект ТаблицаЗначений, где с ними производятся любые манипуляции (поиск строк, удаление, добавление новых, группировка), после чего таблица загружается обратно в набор.

Рассмотрим, как реализовать этот подход на практике:


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

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

// Производим поиск строк по нужным измерениям (эмулируем множественный отбор)
ПараметрыОтбора = Новый Структура;
ПараметрыОтбора.Вставить("Номенклатура", ИскомыйТовар);
ПараметрыОтбора.Вставить("Склад", ИскомыйСклад);

НайденныеСтроки = ТаблицаДвижений.НайтиСтроки(ПараметрыОтбора);

Для Каждого СтрокаДвижения Из НайденныеСтроки Цикл
    // Меняем значения ресурсов в найденных строках
    СтрокаДвижения.Количество = НовоеЗначениеКоличества;
КонецЦикла;

// Загружаем измененную таблицу обратно в набор записей
НаборЗаписей.Загрузить(ТаблицаДвижений);

// Записываем набор в базу данных
НаборЗаписей.ОбменДанными.Загрузка = Истина;
НаборЗаписей.Записать();

Важное примечание: Метод с выгрузкой в таблицу значений удобен, когда алгоритм изменения данных достаточно сложен (например, требуется свернуть таблицу, удалить определенные строки или сопоставить данные с внешним источником запросом).

Способ №3. Добавление новых движений без удаления старых (Режим замещения)

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

Если передать в метод Записать() параметр Ложь, новые строки добавятся к существующим без их предварительного удаления:


НаборЗаписей = РегистрыНакопления.ИмяРегистра.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);

// Мы НЕ вызываем НаборЗаписей.Прочитать(), так как старые записи нам не нужны
НовоеДвижение = НаборЗаписей.Добавить();
НовоеДвижение.Регистратор = СсылкаНаДокумент;
НовоеДвижение.Период = РабочаяДата;
НовоеДвижение.Номенклатура = НоваяНоменклатура;
НовоеДвижение.Количество = ДополнительноеКоличество;

НаборЗаписей.ОбменДанными.Загрузка = Истина;
// Записываем с параметром Замещать = Ложь
НаборЗаписей.Записать(Ложь);

Используйте этот режим с большой осторожностью: при повторном случайном запуске обработки вы рискуете запестрить регистр дублирующимися строками, так как платформа не будет проверять базу на наличие аналогичных записей при записи в режиме «в хвост».

Важные правила оптимизации при работе с движениями

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

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

Проанализировав все варианты, мы видим, что наиболее гибким, быстрым и безопасным решением для записи движений в оборотные регистры накопления из внешних обработок является использование объекта РегистрНакопленияНаборЗаписей с программной фильтрацией строк непосредственно в цикле обхода набора.

← На главную