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