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