В практике программирования на платформе 1С часто возникают ситуации, когда необходимо вмешаться в стандартный процесс проведения документа и изменить данные, которые записываются в регистры. Казалось бы, задача тривиальная: получить записи, изменить значение ресурса и записать обратно. Однако, если делать это неправильно, можно столкнуться с неожиданным поведением системы: записи дублируются, не изменяются вовсе или исчезают при перепроведении.
В этой статье мы подробно разберем ситуацию, когда разработчик пытается изменить записи регистра накопления, используя конструкцию СоздатьНаборЗаписей() внутри модуля объекта документа, и выясним, почему этот подход является ошибочным и к каким последствиям он приводит.
Давайте рассмотрим типичный пример кода, который вызывает проблемы. Представим, что у нас есть задача: при проведении документа обнулить ресурс "Процент" в регистре накопления "Номенклатура". Разработчик помещает следующий код в модуль объекта (например, в процедуру ОбработкаПроведения или после нее):
НаборЗаписей = РегистрыНакопления.Номенклатура.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(Ссылка);
НаборЗаписей.Прочитать();
Для Каждого Запись Из НаборЗаписей Цикл
Запись.Процент = 0;
КонецЦикла;
НаборЗаписей.Записать();
При выполнении этого кода вместо ожидаемого обновления существующих строк часто происходит добавление новых записей или возникают ошибки блокировок. Давайте разберем по шагам, что здесь происходит не так.
Самая главная проблема данного подхода заключается в месте его исполнения. Код размещен в Модуле объекта документа. В момент проведения документа платформа 1С автоматически управляет транзакцией записи движений.
ЭтотОбъект.Движения.Создавая отдельный объект НаборЗаписей через менеджер регистра (РегистрыНакопления.Номенклатура...), мы создаем "параллельный" поток данных к базе, который ничего не знает о том, что документ прямо сейчас уже формирует свои движения в памяти.
Результат конфликта: Вы вручную записываете набор записей методом .Записать(). Но затем, по завершении процедуры проведения, платформа берет коллекцию Движения (в которой могут быть старые или пересчитанные по-стандартному данные) и записывает их поверх ваших изменений, либо добавляет их рядом, если ключи уникальности (номера строк) отличаются.
Важно понимать физику работы метода Записать() для регистров. Многие начинающие разработчики полагают, что этот метод работает как SQL-команда UPDATE (обновление). На самом деле, для регистров накопления, бухгалтерии и сведений (подчиненных регистратору) метод работает иначе.
Метод Записать(Истина) (режим замещения, включенный по умолчанию) выполняет следующие действия на уровне СУБД:
Если же вызвать метод с параметром Ложь — НаборЗаписей.Записать(Ложь) — то старые записи удалены не будут, а новые просто добавятся (допишутся). Это часто является причиной появления дублей, если разработчик не понимает назначения параметра замещения.
Чтобы корректно изменить записи регистра в процессе проведения документа, не нужно создавать новый НаборЗаписей. Необходимо работать с той коллекцией, которая уже существует в контексте документа. Это коллекция Движения.
Давайте рассмотрим, как правильно реализовать задачу обнуления процента внутри модуля объекта (например, в процедуре ОбработкаПроведения).
Сначала документ должен сформировать свои стандартные движения. Это может происходить автоматически (если включено автоматическое удаление движений и формирование) или программно.
// Пример программного формирования движений (упрощенно)
Для Каждого СтрокаТЧ Из Товары Цикл
Движение = Движения.Номенклатура.Добавить();
Движение.Период = Дата;
Движение.Номенклатура = СтрокаТЧ.Номенклатура;
Движение.Процент = 10; // Допустим, изначально рассчитывается так
Движение.Регистратор = Ссылка; // В новых версиях платформы заполняется автоматически при записи
КонецЦикла;
После того как движения сформированы (но еще не записаны в базу!), мы можем перебрать эту коллекцию прямо в памяти и внести необходимые корректировки. Это самый быстрый и надежный способ.
// Проходим по коллекции движений конкретного регистра
Для Каждого Запись Из Движения.Номенклатура Цикл
// Вносим наши изменения
Запись.Процент = 0;
КонецЦикла;
Чтобы платформа поняла, что движения этого регистра нужно сохранить в базу данных, необходимо убедиться, что установлен соответствующий флаг. Обычно, если вы добавляете движения методом Добавить(), флаг взводится автоматически. Но для надежности или при модификации существующих движений лучше указать это явно.
Движения.Номенклатура.Записывать = Истина;
Внимание: Не нужно вызывать метод Движения.Номенклатура.Записать() внутри обработки проведения! Платформа сама запишет все коллекции, у которых свойство Записывать установлено в Истина, после успешного завершения транзакции.
Давайте резюмируем преимущества правильного подхода:
Значит ли это, что СоздатьНаборЗаписей() вообще нельзя использовать? Конечно, нет. Этот метод незаменим в других ситуациях:
Но внутри модуля объекта документа (в процедурах ОбработкаПроведения, ПередЗаписью и т.д.) всегда используйте коллекцию Движения.
Ниже приведен полный пример процедуры проведения, где мы сначала формируем движения, а затем корректируем их:
Процедура ОбработкаПроведения(Отказ, Режим)
// 1. Стандартное формирование движений (пример)
// Обычно здесь вызов процедуры общего модуля или конструктора движений
Движения.Номенклатура.Записывать = Истина;
Для Каждого ТекСтрокаНоменклатура Из ТЧНоменклатура Цикл
Движение = Движения.Номенклатура.Добавить();
Движение.Период = Дата;
Движение.Номенклатура = ТекСтрокаНоменклатура.Номенклатура;
Движение.Склад = Склад;
Движение.Количество = ТекСтрокаНоменклатура.Количество;
Движение.Процент = ТекСтрокаНоменклатура.Процент;
КонецЦикла;
// 2. Наша корректировка (пост-обработка)
// Допустим, нам нужно обнулить процент по какой-то сложной логике перед записью
Для Каждого Запись Из Движения.Номенклатура Цикл
Если Запись.Количество > 100 Тогда
Запись.Процент = 0;
КонецЕсли;
КонецЦикла;
// Явного вызова Записать() здесь нет! Платформа сделает это сама.
КонецПроцедуры
Используя этот подход, вы избежите "дурдома" с дублирующимися записями и обеспечите стабильную работу вашей конфигурации.