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