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