Как оформить строки динамического списка в зависимости от того, просмотрен ли документ пользователем?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практических задачах автоматизации часто возникает необходимость визуально выделять документы в списках (например, выделять жирным шрифтом непрочитанные или серым цветом уже просмотренные). При этом важно, чтобы статус прочтения был индивидуальным для каждого пользователя информационной базы. Рассмотрим, как правильно спроектировать и реализовать этот механизм в платформе 1С:Предприятие 8.3, избежав проблем с производительностью СУБД.

Архитектура хранения данных

Для реализации этой задачи нам потребуется создать независимый регистр сведений (например, под именем ПросмотрыДокументов). Не рекомендуется использовать для этого табличные части самих документов, так как при записи статуса просмотра документ будет блокироваться, а также будет изменяться версия объекта, что крайне нежелательно при совместной работе пользователей.

Спроектируем структуру регистра сведений следующим образом:

  1. Тип регистра: Независимый, непериодический.
  2. Измерение Документ: Тип — ссылка на наш документ (или определяемый тип, если документов несколько).
  3. Измерение Пользователь: Тип — СправочникСсылка.Пользователи.

Сам факт наличия записи в данном регистре для пары "Документ — Пользователь" будет означать, что документ был просмотрен.

Решение 1. Реализация через произвольный запрос динамического списка

Это классический и наиболее простой в реализации способ. Мы свяжем таблицу документов с таблицей нашего регистра сведений с помощью механизма левого соединения.

Рассмотрим по шагам настройку динамического списка в форме списка документов:

Шаг 1. Настройка произвольного запроса. Переведем динамический список в режим произвольного запроса и напишем следующий текст компоновки данных:


ВЫБРАТЬ
    ДокументТаблица.Ссылка КАК Ссылка,
    ДокументТаблица.Проведен КАК Проведен,
    ДокументТаблица.ПометкаУдаления КАК ПометкаУдаления,
    ДокументТаблица.Номер КАК Номер,
    ДокументТаблица.Дата КАК Дата,
    ВЫБОР
        КОГДА Просмотры.Документ ЕСТЬ NULL
            ТОГДА ЛОЖЬ
        ИНАЧЕ ИСТИНА
    КОНЕЦ КАК Просмотрен
ИЗ
    Документ.ИмяВашегоДокумента КАК ДокументТаблица
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ПросмотрыДокументов КАК Просмотры
        ПО ДокументТаблица.Ссылка = Просмотры.Документ
        И Просмотры.Пользователь = &ТекущийПользователь

Важные правила оптимизации при использовании запроса:

Шаг 2. Передача параметров на сервере. В модуле формы списка в процедуре ПриСозданииНаСервере нам необходимо передать значение текущего пользователя в параметры динамического списка:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    Список.Параметры.УстановитьЗначениеПараметра("ТекущийПользователь", Пользователи.ТекущийПользователь());
    
КонецПроцедуры

Шаг 3. Условное оформление. В настройках динамического списка (на вкладке "Условное оформление") добавим новое правило:

Решение 2. Альтернативный вариант через событие ПриПолученииДанныхНаСервере

Если документов в базе миллионы, левое соединение в динамическом списке может создавать избыточную нагрузку на СУБД при интерактивном листании. В этом случае мы можем применить более производительный подход с использованием предопределенного события таблицы формы ПриПолученииДанныхНаСервере.

При этом методе запрос динамического списка остается максимально простым и легким (выбираются только данные документа, без соединений с регистром). Наложение оформления происходит программно только на те строки, которые физически отображаются на экране пользователя (обычно около 30–40 строк).

Разберем программный код этого обработчика в модуле формы списка:


&НаСервереБезКонтекста
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
    
    // Получаем массив ссылок отображаемых на экране документов
    МассивДокументов = Строки.ПолучитьКлючи();
    Если МассивДокументов.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    // Выполняем легкий точечный запрос к регистру сведений
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   Просмотры.Документ КАК Документ
        |ИЗ
        |   РегистрСведений.ПросмотрыДокументов КАК Просмотры
        |ГДЕ
        |   Просмотры.Документ В (&МассивДокументов)
        |   И Просмотры.Пользователь = &ТекущийПользователь";
        
    Запрос.УстановитьПараметр("МассивДокументов", МассивДокументов);
    Запрос.УстановитьПараметр("ТекущийПользователь", Пользователи.ТекущийПользователь());
    
    ВыгрузкаПросмотров = Запрос.Выполнить().Выгрузить();
    ВыгрузкаПросмотров.Индексировать("Документ");
    
    // Обходим строки динамического списка и применяем оформление
    Для Каждого КлючИЗначение Из Строки Цикл
        ТекущаяСтрока = КлючИЗначение.Значение;
        СсылкаНаДокумент = КлючИЗначение.Ключ;
        
        // Если записи в регистре нет, значит документ не просмотрен
        Если ВыгрузкаПросмотров.Найти(СсылкаНаДокумент, "Документ") = Неопределено Тогда
            // Выделяем непросмотренный документ полужирным шрифтом
            ТекущаяСтрока.Оформление["Ссылка"].Шрифт.Установить(Новый Шрифт(,,, Истина));
        КонецЕсли;
    КонецДля;
    
КонецПроцедуры

Оптимизация записи статуса "Просмотрено"

Чтобы снизить нагрузку при записи статусов, в форме самого документа в процедуре ПриОткрытии (или перед закрытием) необходимо выполнять проверку, читал ли текущий пользователь этот документ ранее. Повторная перезапись одной и той же информации не имеет смысла и создает лишние транзакции.

Приведем пример оптимизированного кода записи в модуле формы документа:


&НаСервере
Процедура ЗафиксироватьПросмотрДокументаНаСервере()
    
    ТекущийПользователь = Пользователи.ТекущийПользователь();
    
    // Быстрая проверка существования записи без блокировок
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ 1 ИЗ РегистрСведений.ПросмотрыДокументов КАК Просмотры 
        |ГДЕ Просмотры.Документ = &Документ И Просмотры.Пользователь = &Пользователь";
    Запрос.УстановитьПараметр("Документ", Объект.Ссылка);
    Запрос.УстановитьПараметр("Пользователь", ТекущийПользователь);
    
    Если Запрос.Выполнить().Пустой() Тогда
        // Записываем только если открыли впервые
        МенеджерЗаписи = РегистрыСведений.ПросмотрыДокументов.СоздатьМенеджерЗаписи();
        МенеджерЗаписи.Документ = Объект.Ссылка;
        МенеджерЗаписи.Пользователь = ТекущийПользователь;
        МенеджерЗаписи.Записать(Истина);
    КонецЕсли;
    
КонецПроцедуры

Своевременное обновление интерфейса

Поскольку запись статуса "Просмотрено" происходит внутри формы документа, открытая форма списка "не знает" об этих изменениях и не обновит оформление строки автоматически. Для исправления этого поведения настроим интерактивное взаимодействие форм.

В модуле формы документа после успешной фиксации просмотра вызовем глобальное оповещение системы:


&НаКлиенте
Процедура ПриОткрытии(Отказ)
    // Код фиксации просмотра на сервере...
    Оповестить("ДокументПросмотрен", Объект.Ссылка);
КонецПроцедуры

В модуле формы списка документов найдем или создадим предопределенное событие ОбработкаОповещения и пропишем в нем обновление отображения динамического списка:


&НаКлиенте
Процедура ОбработкаОповещения(ИмяСобытия, Параметр, Источник)
    
    Если ИмяСобытия = "ДокументПросмотрен" Тогда
        // Обновляем список, чтобы условное оформление применилось на клиенте
        Элементы.Список.Обновить();
    КонецЕсли;
    
КонецПроцедуры

Такой подход гарантирует мгновенное обновление интерфейса: как только пользователь закроет форму прочитанного документа, в форме списка данный документ сразу же перестанет выделяться как непрочитанный.

← На главную