Почему при проведении документа удаляются записи в регистре накопления, созданные сторонней обработкой?

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

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

Анализируем стандартное поведение платформы при проведении

Прежде чем приступать к исправлению кода, нам необходимо понять, как платформа управляет движениями. В метаданных каждого документа есть важное свойство — Удаление движений. Рассмотрим подробнее, как этот параметр влияет на записи в регистрах:

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

В рассматриваемой нами ситуации документ имел настройку Не удалять автоматически, но записи все равно пропадали. Это указывает на наличие программной очистки внутри самого модуля документа.

Исследование программного кода и типичные ошибки

Посмотрим на пример кода, который часто пытаются использовать для «подтягивания» существующих записей регистра обратно в документ:


ДвижениеВремени = Движения.НаименованиеМоегоРегистра;
ДвижениеВремени.Отбор.Регистратор.Установить(Ссылка);
ДвижениеВремени.Прочитать();
ДвижениеВремени.Записать(Истина);

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

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

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

Выявление скрытой причины: функции очистки

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


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

Если ваш код по чтению «внешних» движений находится до вызова такой функции, то результаты чтения будут стерты внутри нее. Если после — вы можете столкнуться с тем, что функция уже изменила состояние объекта так, что прочитать данные из базы будет невозможно или они будут неактуальны.

Решение проблемы: создание временного буфера (Backup)

Чтобы гарантированно сохранить записи, созданные внешним механизмом, мы должны реализовать механизм резервного копирования набора записей перед тем, как управление будет передано «опасным» процедурам очистки. Рассмотрим пошаговый алгоритм решения:

  1. Создаем независимый объект НаборЗаписей вне коллекции Движения.
  2. Читаем данные из базы данных по регистратору в этот независимый набор.
  3. Выполняем все необходимые процедуры проведения и очистки.
  4. Восстанавливаем данные из буфера в коллекцию Движения.
  5. Устанавливаем признак записи для этого набора.

Рассмотрим пример реализации этого алгоритма на встроенном языке 1С:


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

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

Альтернативный подход: использование флага Записывать

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


// В самом конце ОбработкиПроведения
Набор = Движения.НаименованиеМоегоРегистра;
Набор.Отбор.Регистратор.Установить(Ссылка);
Набор.Прочитать(); // Читаем то, что было в базе до начала проведения
Набор.Записывать = Истина; // Флаг гарантирует, что платформа не «забудет» эти данные

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

Почему обновление платформы могло повлиять на ситуацию?

В обсуждении упоминалось, что проблема обострилась после обновления платформы (например, на версию 8.3.18). Это не редкость. Фирма «1С» постоянно оптимизирует механизмы работы с базой данных. В новых версиях платформы:

Если конфигурация работает в режиме совместимости с версиями 8.2 или ранними 8.3, то на новой платформе эмуляция старого поведения может работать нестабильно при наличии нестандартных «вклиниваний» в процесс проведения. Поэтому всегда проверяйте свойство Режим совместимости в корне конфигурации после обновления.

Рекомендации по архитектуре

Чтобы избежать подобных проблем в будущем, проанализируем архитектурную чистоту решения. Ситуация, когда документ сам не знает о своих движениях (потому что их делает внешняя обработка), считается антипаттерном в разработке на 1С. Правильнее будет:

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

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

← На главную