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