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