Почему дублируются движения при перепроведении документа в обычных формах и как это исправить?

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

В процессе разработки на платформе «1С:Предприятие 8» программисты часто сталкиваются с ситуацией, когда при интерактивном перепроведении документа в режиме обычного приложения (толстый клиент) записи в регистрах начинают «множиться». Каждое нажатие кнопки «Провести» добавляет новые наборы записей к уже существующим, вместо того чтобы замещать их. Давайте подробно разберем, почему это происходит, как работает механизм коллекций движений и какими способами можно обеспечить корректную работу системы.

Причина поведения: жизненный цикл объекта в памяти

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

В обычном приложении ситуация иная. Объект документа (переменная ЭтотОбъект) «живет» в оперативной памяти формы всё то время, пока эта форма открыта пользователем. Проанализируем цепочку событий:

  1. Пользователь открывает документ и нажимает «Провести».
  2. Платформа заполняет коллекцию Движения.МойРегистр и записывает её в базу данных.
  3. Пользователь, не закрывая форму, снова нажимает «Провести».
  4. В этот момент коллекция Движения.МойРегистр в памяти объекта уже содержит записи от предыдущего проведения.
  5. Если в коде обработчика ОбработкаПроведения используются методы добавления (например, Добавить() или Загрузить()), то новые записи просто приплюсовываются к тем, что уже лежат в коллекции.
  6. При записи набора в базу данных (если не включена автоматическая очистка) система видит «старые + новые» записи и сохраняет их все.

Именно поэтому, если закрыть форму и открыть её снова, проблема временно исчезает: при открытии создается новый экземпляр объекта с пустой коллекцией движений (если они не считываются специально).

Способ 1: Настройка метаданных «Удаление движений»

Самый простой и рекомендуемый платформенный способ решения проблемы — правильная настройка свойств самого объекта «Документ» в дереве метаданных. Рассмотрим свойство «Удаление движений». Оно может принимать три значения:

  1. Удалять автоматически: Платформа перед вызовом обработчика ОбработкаПроведения сама очищает движения документа как в базе данных, так и в коллекции объекта. Это гарантирует, что при каждом перепроведении вы работаете с пустым набором.
  2. Не удалять автоматически: Платформа не предпринимает никаких действий. Ответственность за очистку старых записей полностью ложится на разработчика.
  3. Удалять автоматически при отмене проведения: Движения будут удалены только при явной отмене проведения документа пользователем, но при обычном перепроведении они останутся на месте.

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

Способ 2: Программная очистка движений в коде

Если специфика вашей конфигурации требует установки режима «Не удалять автоматически», необходимо реализовать ручную очистку. Посмотрим на пример кода, который часто применяется в типовых решениях (например, в УТ 10.3):


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

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

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

Способ 3: Использование флага «Записывать»

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

Проанализируем ситуацию, когда мы комбинируем эти методы. Если вы в начале процедуры делаете Записать(), а в конце — Записывать = Истина, может возникнуть избыточная нагрузка на СУБД из-за двойного обращения к таблицам регистра. Правильным подходом при ручном управлении будет либо использование только явной записи в конце процедуры, либо настройка очистки в самом начале с последующей автоматической записью.

Нюансы метода Записать(Замещать)

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

В контексте нашей проблемы, вызов Движения.МойРН.Записать(Истина) при неочищенной коллекции в памяти и есть причина «размножения» — мы записываем в базу старые данные, к которым уже успели добавить новые в коде проведения.

Рекомендации для разработчиков

Подведем итоги и сформируем алгоритм действий для предотвращения дублирования движений в обычном приложении:

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

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

← На главную