Что и зачем
Очень часто в моей жизни абстрактная Зинаида добивается неимоверных высот в работе с СКД и хочет, чтобы её прекраснейшим, до мельчайших деталей настроенным, отчётом мог восхищаться если не весь её отдел, то хотя бы закадычная Анжела. И начинается проблема: как бы у Анжелы всё настроить так же идеально, чтобы и ей было так же удобно. Или Анжела сама увидела, как у Зинаиды всё здорово, удобно и красненьким выделено, реквизиты в отдельных колонках стоят, все вычисляемые поля округлены и так чудесно отформатированы. Само собой, Анжела возжелает того же.
Часто руководитель отдела тратит своё драгоценное время, чтобы настроить единый отчёт для своих подчиненных для отчётности в едином формате. Но вариант сотрудникам нужно как-то раздать.
И даже бывает, что коммерческий директор согласует с партнёрами новый показатель качества работы компании, создаёт новый вариант отчёта по фондам, и ему нужно раздать его ниже по управлению компании.
И нельзя забывать про моменты, когда человек идёт в отпуск, и ему бы кому-то передать пару очень важных отчётов, чтобы за него держали руку на пульсе. Не передавать же аккаунт от 1С.
И во все эти моменты начинается боль. Или пользователей, или наша, то есть программистов. За время жизни с 1С я узнал целых три способа решения этой проблемы:
- Первобытный – когда Зинаида, руководитель отдела, или, о боже, топ-менеджер сам ходит и настраивает за компьютерами подчиненных такой же отчёт, пытаясь вспомнить все ползунки, галочки, кнопочки или даже по памяти прописать пользовательские поля выражения. Минусы очевидны - это и трудоёмкость, и неточность такого переноса варианта отчёта.
- Средневековый – когда творец отчёта уже очень продвинутый пользователь, он выгружает xml файл настроек, отправляет его на почту / в файлообменник / несёт на флешке / скидывает в мессенджер, а потом пытается объяснить чуть менее продвинутым пользователям, какие кнопки жать, чтобы файл этот себе применить. Минусы не такие трагичные, но это и что-то объяснять подчиненным, и передача файлов, что всё еще довольно трудоёмко, особенно если мы говорим о компаниях, в которых сотрудники работают с терминальных серверов, на которых доступ к почтам и файлообменникам предпочитают отключать.
- Делегирующий – когда звонят уже нам (программистам, разработчикам, саппортам, эникейщикам, руководителям ИТ отдела и прочим несчастным и сочувствующим), и это становится уже нашей проблемой. Для нас опять же есть способ ленивый и неленивый:
- Ленивый – сделать вариант отчёта общим. Но если часто так делать, то общих вариантов отчёта станет супер много, и пострадают невинные пользователи, которым аналитика корма для жуков не нужна, а в общих вариантах теперь красуется 12 отчетов по всем филиалам с этим кормом.
- Неленивый – копировать вариант отчёта конкретному пользователю. Но можно очень сильно замучаться, особенно если отчётность меняется часто, а компания крупная.
Также когда мы сами копируем вариант пользователю, а он хотел только посмотреть, но не постоянно им пользоваться, может случиться, что Маша / Петя / Таня скажут «не – фигня, мне такого не надо», и придётся еще раз идти и удалять у человека свежескопированный вариант.
И что же делать
Было принято волевое решение придумать что-то такое, чтобы любой пользователь А в пару кликов мог отправить отчёт. Пользователь Б, в свою очередь, может посмотреть отправленный отчёт и сохранить его себе только если отчёт нравится и правда нужен. ОЧЕНЬ важно использовать только 1С, без почт, файлов, записок и звонков с диктовкой инструкции из 40 действий.
Делим задачу на:
- Что отправить?
- Как отправить?
- Как получить?
- Как открыть?
Сразу ремарка, что всё нижеизложенное справедливо для БСП 2.4.1.84, если у вас редакция 3+, то я абсолютно не в курсе будет ли всё работать так же как у меня. Попробовать на новых БСП никто не мешает, наверняка там из коробки не заработает, но подпилить, надеюсь, не будет сильно сложно.
Сначала, мы обращаем внимание, что в любом отчёте, если у него не используется собственная форма, то ссылка будет вести на элемент справочника вариантов отчётов. Потому что при открытии отчёта открывается форма справочника вариантов отчёта, всё легко.
Значит, у нас есть навигационная ссылка на вариант отчёта. И если мы попробуем её открыть, то у нас она откроется именно на этом варианте. ВСЁ! Ура! Задача решена, не успев начаться. Уж навигационную ссылку можно и через аську скинуть.
Но так безоблачно сработает только в двух случаях:
- Все пользователи с полными правами на справочник (а значит и так видят отчёты друг друга, что мы тут изобретаем - не понятно)
- Мы даём ссылку на общий вариант отчёта (а зачем мы это делаем не понятно, его и так все видят)
Во всех остальных случаях беспощадное разделение данных не пустит нас перейти по навигационной ссылке чужого отчёта.
В моей конфигурации РЛС на варианты отчётов выглядит вот так.
У вас может быть слегка по-другому, но суть должна быть та же самая, все видят НЕ Пользовательские варианты ИЛИ если вариант ТолькоДляАвтора, то только Автор его и видит.
Это значит, что если пользователь перейдёт по навигационной ссылке чужого варианта, у него будет ошибка прав доступа. Если это РЛС убрать, то все будут видеть варианты всех пользователей, а это очень уж неудобно. Тогда придётся решать задачу «как в помойке найти свой отчёт».
Значит, Что отправить? мы поняли, но при Как открыть? нам надо будет учитывать, что мы открываем чужой вариант и просто переход по ссылке не сработает от слова совсем.
Можно сделать совсем плохо и вместо перехода по ссылке копировать вариант текущему пользователю и только после этого открывать его, но тогда он сохранится, а нам этого не надо, всё равно придётся удалять его у пользователя. Совсем плохо делать не будем, благо идеи еще есть.
Две следующие проблемы Как отправить? и Как получить? я уже решил в статье
Собственная начальная страница с избранным, историей и сообщениями и вероломно сошлюсь сам на себя, третий блок этой статьи как раз посвящен тому, как пользователи могут отправлять друг другу навигационные ссылки средствами одной только 1С. Всё это дублировать здесь я не буду, потому что статья станет совсем большой, непонятной и во все стороны пугающей.
В итоге, используя наработки из той статьи, мы приходим к тому, что у нас есть гиперссылки. Их нам нужно не открыть, а как-то понять, что это вариант отчёта. Затем ощутить, что он чужой и открыть этот отчёт, применив настройки чужого варианта.
Для всех остальных ссылок мы всё также используем ПерейтиПоНавигационнойСсылке(НавигационнаяСсылка)
Как открыть?
В первую очередь проверяем текущую навигационную ссылку - на какой объект она нас ведёт. Сделаем сначала базовую функцию, что пускает по всем навигационным ссылкам кроме вариантов отчёта:
НавигационнаяСсылка = НавигационныеСсылки.Получить(Элемент.Имя);
Если ЗначениеЗаполнено(НавигационнаяСсылка) Тогда
Результат = ПроверитьИПреобразоватьНавигационнуюСсылку(НавигационнаяСсылка);
Если Результат.ОбъектДоступен Тогда
ПерейтиПоНавигационнойСсылке(НавигационнаяСсылка);
Иначе
Сообщить("Нет прав на открытие объекта");
КонецЕсли;
КонецЕсли;
И сама функция ПроверитьИПреобразоватьНавигационнуюСсылку
&НаСервереБезКонтекста
Функция ПроверитьИПреобразоватьНавигационнуюСсылку(НавигационнаяСсылка)
Результат = Новый Структура("ОбъектДоступен", Истина);
Если СтрНайти(НавигационнаяСсылка, "Справочник.ВариантыОтчетов") Тогда
//здесь мы должны в дальнейшем проверить доступен ли нам этот вариант отчёта да и отчёт вообще
Результат.Вставить("ОбъектДоступен", Ложь);
КонецЕсли;
Возврат Результат;
КонецЕсли
Чтобы проверить доступность варианта отчёта нам надо получить сам вариант отчёта. Неожиданно, но это так. Уходим в задачу «получить ссылку из навигационной ссылки». Решений множество, но мне нравятся два:
Сначала о простом решении, это конец навигационной ссылки:
?ref=83981866dab567cd11ec1467b0ca307c
А это гуид этого же объекта
b0ca307c-1467-11ec-8398-1866dab567cd,
Чтобы легче было понять, в чем суть простого решения, выделю цветом разные фрагменты
83981866dab567cd11ec1467b0ca307c И b0ca307c-1467-11ec-8398-1866dab567cd
Всё что от нас требуется- это пересобрать строку используя Сред Лев Прав
Полученную перестановку подставить в конструктор УникальныйИдентификатор(Строка)
А по UID уже получить ссылку через СправочникСсылка.<Имя справочника>, метод УникальныйИдентификатор()
Но сегодня мы попробуем использовать изящное решение, еще и на будущее сделаем его универсальным, чтобы можно было этой функцией по навигационной ссылке достать любой объект, а не только вариант отчёта. Мало ли еще где пригодится.
Имеем e1cib/data/Справочник.ВариантыОтчетов?ref=90de0002c9568f9611ee034a69065907
ПрефиксНавигСсылки = "e1cib/data/";
ТочкаПослеПрефикса = Найти(НавигационнаяСсылка, ПрефиксНавигСсылки) + СтрДлина(ПрефиксНавигСсылки);
//ТочкаПослеПрефикса это позиция где начинается «Справочник.ВариантыОтчетов»
ПараметрИдентификатора = "?ref=";
ТочкаПередИдентификатором = Найти(НавигационнаяСсылка, ПараметрИдентификатора);
//ТочкаПередИдентификатором это позиция где начинается «?ref=»
//Значит «Справочник.ВариантыОтчетов» находится между ТочкаПослеПрефикса И ТочкаПередИдентификатором, получим эту строку
ПредставлениеТипа = Сред(НавигационнаяСсылка, ТочкаПослеПрефикса, ТочкаПередИдентификатором - ТочкаПослеПрефикса);
//Мы можем использовать ЗначениеВСтрокуВнутр для пустой ссылки, тогда, сама ссылка будет представлена множеством нулей. Чтобы получить пустую ссылку по представлению типа, используем функцию ПредопределенноеЗначение. В итоге имеем:
ШаблонСистемногоПредставления = ЗначениеВСтрокуВнутр(ПредопределенноеЗначение(ПредставлениеТипа + ".ПустаяСсылка"));
//Получаем вот такой шаблон пустой ссылки варианта отчёта {"#",b7ea0510-1c17-417a-b55c-5e6720efe4fd,109:00000000000000000000000000000000}
//Теперь нам нужно заменить нули на всё, что у нас после ?ref
ЗначениеСсылки = СтрЗаменить(ШаблонСистемногоПредставления, "00000000000000000000000000000000", Сред(НавигационнаяСсылка, ТочкаПередИдентификатором + СтрДлина(ПараметрИдентификатора)));
//Получаем вот такое системное представление нашей ссылки {"#",b7ea0510-1c17-417a-b55c-5e6720efe4fd,109:90de0002c9568f9611ee034a69065907}
//И всё что нам остаётся - это получить ссылку из строки обратно:
Ссылка = ЗначениеИзСтрокиВнутр(ЗначениеСсылки);
УРА у нас есть ссылка, и, что ОЧЕНЬ важно мы её получили путём строковых преобразований и системных отображений. Если у нас нет прав на неё, то там будет ужасный ОбъектНеНайден, но сама ссылка всё равно будет валидна и в ошибку ничего не упадёт.
Чтобы понять, что у нас нет прав на ссылку, делаем следующий микрозапрос
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ
| ВариантыОтчетов.Ссылка КАК Ссылка
|ИЗ
| Справочник.ВариантыОтчетов КАК ВариантыОтчетов
|ГДЕ
| ВариантыОтчетов.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
Если Результат запроса пустой, тогда мы получили вариант отчёта, на который у нас нет прав. А значит - это именно чужой вариант, который мы хотим открыть и посмотреть.
Cоздаём функцию в любом привилегированном модуле (я выбрал ПользователиПривилегированный). Функция вернёт нам всё, что необходимо пользователю, чтобы у него открылся вариант. Можно использовать привилегированный режим прямо в обработке, но если вдруг вы захотите подключить ее как дополнительную или открывать как внешнюю, то начнётся возня с безопасным режимом открытия обработок
ТАК ЧТО ЛУЧШЕ ДОБАВИТЬ ФУНКЦИЮ В ПРИВИЛЕГИРОВАННЫЙ ОБЩИЙ МОДУЛЬ
Или самостоятельно добавить её в обработке и не забыть привилегированный вызов.
Результат = ПользователиПривилегированный.ПолучитьДанныеВариантаОтчёта(Ссылка);
Функция ПолучитьДанныеВариантаОтчёта(ВариантОтчета) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВариантыОтчетов.Отчет.Имя КАК ИмяОтчета,
| ВариантыОтчетов.Наименование КАК НаименованиеВарианта,
| ВариантыОтчетов.Настройки КАК КлючХранилищаНастроек
|ИЗ
| Справочник.ВариантыОтчетов КАК ВариантыОтчетов
|ГДЕ
| ВариантыОтчетов.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", ВариантОтчета);
РезультатЗапроса = Запрос.Выполнить();
ВыборкаДанныеОтчета = РезультатЗапроса.Выбрать();
Если ВыборкаДанныеОтчета.Следующий() Тогда
Результат = Новый Структура("ОбъектДоступен, ИмяОтчета, КлючХранилищаНастроек, НаименованиеВарианта", Ложь, ВыборкаДанныеОтчета.ИмяОтчета, ВыборкаДанныеОтчета.КлючХранилищаНастроек, ВыборкаДанныеОтчета.НаименованиеВарианта);
Иначе
Результат = Новый Структура("ОбъектДоступен, ИмяОтчета", Ложь, Неопределено);
КонецЕсли;
Возврат Результат;
КонецФункции
Теперь немаловажный момент
Возможно, нам отправили не просто вариант, на который нет прав, но и на этот отчёт в принципе у нас нет доступа. Этот момент также нужно проверить, иначе мы, несмотря на все старания, всё равно получим в итоге ошибку прав доступа. Так что в конце функции ПроверитьИПреобразоватьНавигационнуюСсылку добавляем
Если НЕ ПравоДоступа("Просмотр", Метаданные.Отчеты[Результат.ИмяОтчета]) Тогда
Результат.ИмяОтчета = Неопределено;
КонецЕсли;
В итоге, функция ПроверитьИПреобразоватьНавигационнуюСсылку вернёт нам структуру где:
- ОбъектДоступен = Истина, если вариант доступен и его можно открыть
- ОбъектДоступен = Ложь и ИмяОтчета = Неопределено, если сам отчёт недоступен
- ОбъектДоступен = Ложь , имя отчёта по метаданным, ключ настроек и имя варианта, если сам отчёт доступен, а переданный вариант нет.
Вот полная функция ПроверитьИПреобразоватьНавигационнуюСсылку если вы запутались
&НаСервереБезКонтекста
Функция ПроверитьИПреобразоватьНавигационнуюСсылку(НавигационнаяСсылка)
Результат = Новый Структура("ОбъектДоступен", Истина);
Если СтрНайти(НавигационнаяСсылка, "Справочник.ВариантыОтчетов") Тогда
ПрефиксНавигСсылки = "e1cib/data/";
ТочкаПослеПрефикса = Найти(НавигационнаяСсылка, ПрефиксНавигСсылки) + СтрДлина(ПрефиксНавигСсылки);
ПараметрИдентификатора = "?ref=";
ТочкаПередИдентификатором = Найти(НавигационнаяСсылка, ПараметрИдентификатора);
ПредставлениеТипа = Сред(НавигационнаяСсылка, ТочкаПослеПрефикса, ТочкаПередИдентификатором - ТочкаПослеПрефикса);
ШаблонСистемногоПредставления = ЗначениеВСтрокуВнутр(ПредопределенноеЗначение(ПредставлениеТипа + ".ПустаяСсылка"));
ЗначениеСсылки = СтрЗаменить(ШаблонСистемногоПредставления, "00000000000000000000000000000000", Сред(НавигационнаяСсылка, ТочкаПередИдентификатором + СтрДлина(ПараметрИдентификатора)));
Ссылка = ЗначениеИзСтрокиВнутр(ЗначениеСсылки);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ
| ВариантыОтчетов.Ссылка КАК Ссылка
|ИЗ
| Справочник.ВариантыОтчетов КАК ВариантыОтчетов
|ГДЕ
| ВариантыОтчетов.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", Ссылка);
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
Результат = ПользователиПривилегированный.ПолучитьДанныеВариантаОтчёта(Ссылка);
//если у пользователя прав на отчет нет, то зануляем имя отчета
Если НЕ ПравоДоступа("Просмотр", Метаданные.Отчеты[Результат.ИмяОтчета]) Тогда
Результат.ИмяОтчета = Неопределено;
КонецЕсли;
КонецЕсли;
КонецЕсли;
Возврат Результат;
КонецФункции
Зная ключ настроек, мы можем текущему пользователю открыть такой же вариант как отправленный. А значит осталось только применить настройки и открыть отчёт, на текущий момент момент функция ПерейтиПоСсылке у нас выглядит следующим образом
&НаКлиенте
Процедура ПерейтиПоСсылке(Элемент, СтандартнаяОбработка)
НавигационнаяСсылка = НавигационныеСсылки.Получить(Элемент.Имя);
Если ЗначениеЗаполнено(НавигационнаяСсылка) Тогда
Результат = ПроверитьИПреобразоватьНавигационнуюСсылку(НавигационнаяСсылка);
Если Результат.ОбъектДоступен Тогда
ПерейтиПоНавигационнойСсылке(НавигационнаяСсылка);
ИначеЕсли Результат.ИмяОтчета = Неопределено Тогда
Сообщить("Нет прав на открытие объекта");
Иначе
//Здесь мы будем получать и открыть вариант отчёта
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Еще нам заранее понадобится функция получающая настройки из хранилища по их ключу
&НаСервереБезКонтекста
Функция ПолучитьНастройки(КлючХранилищаНастроек)
НастройкиВарианта = КлючХранилищаНастроек.Получить();
Возврат НастройкиВарианта;
КонецФункции
Всё, мы готовы открыть форму чужого варианта
Форма = ПолучитьФорму("Отчет." + Результат.ИмяОтчета + ".Форма", Новый Структура());
//Получаем настройки и заполняем их на полученной форме отчёта
Настройки = ПолучитьНастройки(Результат.КлючХранилищаНастроек);
Форма.Отчет.КомпоновщикНастроек.ЗагрузитьНастройки(Настройки);
//Инициируем применение загруженных настроек
ПараметрыЗаполнения = Новый Структура("ВариантМодифицирован, ПользовательскиеНастройкиМодифицированы, СброситьПользовательскиеНастройки", Истина, Истина, Истина);
Форма.ВсеНастройкиЗавершение(ПараметрыЗаполнения, Неопределено);
//Делаем красивый заголовочек
НаименованиеПереданногоВарианта = "Переданный отчёт: " + Результат.НаименованиеВарианта;
Форма.Заголовок = НаименованиеПереданногоВарианта;
Форма.ОтчетНаименованиеТекущегоВарианта = НаименованиеПереданногоВарианта;
//Инициируем новый ключ на открытый вариант отчёта. Иначе система будет думать, что это не какой-то новый отчёт, а последний открытый, уже сохраненный, вариант текущего пользователя. Еще и перезатрёт его при сохранении даже ничего не спросив, а так хотя бы спросит имя варианта при сохранении.
НовыйКлючОтчёта = Новый УникальныйИдентификатор;
Форма.ВариантыПанелиКлючТекущегоВарианта = НовыйКлючОтчёта;
Форма.КлючТекущегоВарианта = НовыйКлючОтчёта;
//И наконец-то открываем форму
Форма.Открыть();
И всё прекрасно открылось.
Все цели достигнуты, всё работает. Теперь пользователи могут делиться между собой вариантами отчётов!
Во вложении я оставлю полную обработку из предыдущей статьи с возможность открытия ссылок на чужие варианты отчётов. Всё тестировалось на версии 1С:Предприятие 8.3 (8.3.15.1656) и БСП 2.4.1.84
Огромное спасибо всем, кто не пожалел своего времени и дочитал до конца!