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

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

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

В этой статье мы подробно разберем ситуацию, когда разработчик пытается изменить записи регистра накопления, используя конструкцию СоздатьНаборЗаписей() внутри модуля объекта документа, и выясним, почему этот подход является ошибочным и к каким последствиям он приводит.

Анализ проблемного кода

Давайте рассмотрим типичный пример кода, который вызывает проблемы. Представим, что у нас есть задача: при проведении документа обнулить ресурс "Процент" в регистре накопления "Номенклатура". Разработчик помещает следующий код в модуль объекта (например, в процедуру ОбработкаПроведения или после нее):


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

Для Каждого Запись Из НаборЗаписей Цикл 
    Запись.Процент = 0; 
КонецЦикла; 

НаборЗаписей.Записать();

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

Ошибка 1: Конфликт контекста и транзакций

Самая главная проблема данного подхода заключается в месте его исполнения. Код размещен в Модуле объекта документа. В момент проведения документа платформа 1С автоматически управляет транзакцией записи движений.

  1. Когда документ проводится, система формирует движения (записи в регистры) в памяти.
  2. Эти движения хранятся в коллекции ЭтотОбъект.Движения.
  3. В конце транзакции платформа автоматически сбрасывает содержимое этой коллекции в базу данных.

Создавая отдельный объект НаборЗаписей через менеджер регистра (РегистрыНакопления.Номенклатура...), мы создаем "параллельный" поток данных к базе, который ничего не знает о том, что документ прямо сейчас уже формирует свои движения в памяти.

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

Ошибка 2: Механизм работы метода Записать()

Важно понимать физику работы метода Записать() для регистров. Многие начинающие разработчики полагают, что этот метод работает как SQL-команда UPDATE (обновление). На самом деле, для регистров накопления, бухгалтерии и сведений (подчиненных регистратору) метод работает иначе.

Метод Записать(Истина) (режим замещения, включенный по умолчанию) выполняет следующие действия на уровне СУБД:

  1. DELETE: Удаляет все записи из таблицы регистра, соответствующие установленному Отбору (в нашем случае — по Регистратору).
  2. INSERT: Вставляет новые записи, находящиеся в памяти набора.

Если же вызвать метод с параметром Ложь — НаборЗаписей.Записать(Ложь) — то старые записи удалены не будут, а новые просто добавятся (допишутся). Это часто является причиной появления дублей, если разработчик не понимает назначения параметра замещения.

Правильное решение: Использование коллекции Движения

Чтобы корректно изменить записи регистра в процессе проведения документа, не нужно создавать новый НаборЗаписей. Необходимо работать с той коллекцией, которая уже существует в контексте документа. Это коллекция Движения.

Давайте рассмотрим, как правильно реализовать задачу обнуления процента внутри модуля объекта (например, в процедуре ОбработкаПроведения).

Шаг 1: Формирование стандартных движений

Сначала документ должен сформировать свои стандартные движения. Это может происходить автоматически (если включено автоматическое удаление движений и формирование) или программно.


// Пример программного формирования движений (упрощенно)
Для Каждого СтрокаТЧ Из Товары Цикл
    Движение = Движения.Номенклатура.Добавить();
    Движение.Период = Дата;
    Движение.Номенклатура = СтрокаТЧ.Номенклатура;
    Движение.Процент = 10; // Допустим, изначально рассчитывается так
    Движение.Регистратор = Ссылка; // В новых версиях платформы заполняется автоматически при записи
КонецЦикла;

Шаг 2: Пост-обработка коллекции Движения

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


// Проходим по коллекции движений конкретного регистра
Для Каждого Запись Из Движения.Номенклатура Цикл
    // Вносим наши изменения
    Запись.Процент = 0;
КонецЦикла;

Шаг 3: Установка флага записи

Чтобы платформа поняла, что движения этого регистра нужно сохранить в базу данных, необходимо убедиться, что установлен соответствующий флаг. Обычно, если вы добавляете движения методом Добавить(), флаг взводится автоматически. Но для надежности или при модификации существующих движений лучше указать это явно.

Движения.Номенклатура.Записывать = Истина;

Внимание: Не нужно вызывать метод Движения.Номенклатура.Записать() внутри обработки проведения! Платформа сама запишет все коллекции, у которых свойство Записывать установлено в Истина, после успешного завершения транзакции.

Почему подход через "Движения" лучше?

Давайте резюмируем преимущества правильного подхода:

  1. Целостность данных: Все изменения происходят в рамках одной транзакции документа. Если проведение отменится (например, из-за ошибки в другом месте), изменения в регистре также не применятся.
  2. Производительность: Мы не делаем лишних запросов к базе данных (чтение набора записей) и лишних записей. Все происходит в оперативной памяти сервера перед финальной фиксацией.
  3. Отсутствие рассинхронизации: Исключается ситуация, когда данные в документе и данные в регистре разъезжаются при перепроведении. Если вы перепроведете документ, алгоритм снова выполнится корректно.

Когда можно использовать НаборЗаписей?

Значит ли это, что СоздатьНаборЗаписей() вообще нельзя использовать? Конечно, нет. Этот метод незаменим в других ситуациях:

Но внутри модуля объекта документа (в процедурах ОбработкаПроведения, ПередЗаписью и т.д.) всегда используйте коллекцию Движения.

Итоговый пример правильного кода

Ниже приведен полный пример процедуры проведения, где мы сначала формируем движения, а затем корректируем их:


Процедура ОбработкаПроведения(Отказ, Режим)
    
    // 1. Стандартное формирование движений (пример)
    // Обычно здесь вызов процедуры общего модуля или конструктора движений
    Движения.Номенклатура.Записывать = Истина;
    Для Каждого ТекСтрокаНоменклатура Из ТЧНоменклатура Цикл
        Движение = Движения.Номенклатура.Добавить();
        Движение.Период = Дата;
        Движение.Номенклатура = ТекСтрокаНоменклатура.Номенклатура;
        Движение.Склад = Склад;
        Движение.Количество = ТекСтрокаНоменклатура.Количество;
        Движение.Процент = ТекСтрокаНоменклатура.Процент;
    КонецЦикла;
    
    // 2. Наша корректировка (пост-обработка)
    // Допустим, нам нужно обнулить процент по какой-то сложной логике перед записью
    Для Каждого Запись Из Движения.Номенклатура Цикл
        Если Запись.Количество > 100 Тогда
             Запись.Процент = 0;
        КонецЕсли;
    КонецЦикла;
    
    // Явного вызова Записать() здесь нет! Платформа сделает это сама.

КонецПроцедуры

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

← На главную