Почему при вызове Записать(Ложь) в обработке проведения пропадают движения документа?

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

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

Однако при попытке вызвать Движения.ИмяРегистра.Записать(Ложь) непосредственно внутри процедуры ОбработкаПроведения разработчики нередко сталкиваются с тем, что движения либо не сохраняются вовсе, либо исчезают при повторном проведении документа. Проанализируем механику работы платформы, выясним причины такого поведения и разберем корректные способы решения проблемы.

В чем заключается конфликт между ручной и автоматической записью движений

Рассмотрим стандартный шаблон кода, который генерируется конструктором движений при настройке документа:


Движения.ОстаткиНаСкладах.Записывать = Истина;
Для Каждого ТекСтрока Из СписокНоменклатуры Цикл
    Движение = Движения.ОстаткиНаСкладах.Добавить();
    Движение.ВидДвижения = ВидДвиженияНакопления.Приход;
    Движение.Период = Дата;
    Движение.Номенклатура = ТекСтрока.Номенклатура;
    Движение.Склад = ТекСтрока.Склад;
    Движение.Количество = ТекСтрока.Количество;
КонецЦикла;
Движения.ОстаткиНаСкладах.Записать(Ложь);

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

  1. Установка признака автоматической записи: Строка Движения.ОстаткиНаСкладах.Записывать = Истина сообщает платформе, что по завершении процедуры ОбработкаПроведения данный набор записей должен быть автоматически сохранен в базу данных.
  2. Ручная запись набора: Вызов Движения.ОстаткиНаСкладах.Записать(Ложь) выполняет запись накопленных в наборе строк непосредственно в базу данных в режиме добавления. При этом после выполнения записи состояние набора в оперативной памяти может очищаться или терять модифицированность.
  3. Автоматическая запись по окончании процедуры: Когда выполнение процедуры доходит до строки КонецПроцедуры, платформа проверяет свойства элементов коллекции Движения. Поскольку флаг Записывать остался равным Истина, платформа автоматически выполняет метод Записать() для всей коллекции движений.
  4. Перезапись пустым набором: Системный метод автоматической записи движений документа всегда выполняется с замещением (параметр Замещать = Истина). В результате пустой или уже сброшенный набор из памяти записывается поверх только что добавленных данных, полностью уничтожая результаты ручной записи.

Способ 1. Отключение автоматической записи движений

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

Посмотрим на рабочий пример кода:


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

В этом случае платформа видит, что у набора ОстаткиНаСкладах признак Записывать равен Ложь, и не пытается повторно применить замещение в момент выхода из ОбработкаПроведения.

Подводные камни использования Записать(Ложь) в регистрах

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

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

Способ 2. Правильный архитектурный подход: чтение, модификация и запись с замещением

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

Если вам действительно необходимо сохранить ранее сформированные движения и добавить к ним новые, правильнее всего объединить их в памяти через предварительное чтение набора:


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

Проанализируем преимущества такого подхода:

  1. Метод Прочитать() загружает существующие строки из базы данных в коллекцию набора записей.
  2. При последующих вызовах Добавить() свойство НомерСтроки инкрементируется корректно, исключая дублирование индексов строк и ошибки на уровне СУБД.
  3. Набор сохраняется целиком со стандартным флагом Замещать = Истина (либо через вызов мДвижения.Записать(Истина), либо автоматически платформой при мДвижения.Записывать = Истина).
  4. Данные не затираются, сохраняется целостность транзакции проведения и поддерживается повторяемость результатов при перепроведении документа.
← На главную