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