Как ограничить доступ к базе, но разрешить печатать отчеты?
Как отладить отчеты там, где они исполняются?
Как перестать страдать из-за разных версий одного отчета?
Как сделать так, чтобы пользователи работали в одной базе?
Зачастую в организациях, где существует несколько баз, полностью учет со всеми проверками ведется в единственной базе. Остальные при этом нужны для ведения оперативного учета и формирования печатных форм. И учет в них ведется по принципу «лишь бы печатались документы».
Рано или поздно, при таком раскладе, появляется необходимость сформировать отчет, данные для которого есть в старшей базе, а потенциальные пользователи которого, работают в остальных.
И тут либо надо добавиться полноты ввода данных во всех базах со всеми выгрузками и проверками, либо пускать этих пользователей в старшую базу.
Предположим, что пользователь это оператор, который может наделать дел, проведя, что не следует или, заглянув, куда не хотелось бы.
Вариант №1.
Можно поставить ему набор прав «бухгалтер» (или аналогичное) и порезать интерфейс. Все хорошо, отчеты печатаются, все довольны. Но есть нюансы:
- Если оператор умен, он сможет открыть все, что доступно бухгалтеру, и сделать свое черное дело;
- Он вынужден сидеть в двух базах. В одной для работы, а в другой для отчетов.
Внешняя обработка с макетом и формой с параметрами и кнопкой сформировать:
&НаКлиенте
Процедура Сформировать(Команда)
ВывестиТабличныйДокумент();
КонецПроцедуры
&НаСервере
Процедура ВывестиТабличныйДокумент()
ТабДокумент.Очистить();
ПараметрыОтчета = Новый Структура;
ПараметрыОтчета.Вставить("НачалоПериода", Отчет.НачалоПериода);
ПараметрыОтчета.Вставить("КонецПериода", КонецДня(Отчет.КонецПериода));
ПараметрыОтчета.Вставить("Организация", Отчет.Организация);
ПараметрыОтчета.Вставить("ВидРМ", Отчет.ВидРМ);
ПараметрыОтчета.Вставить("ПоказыватьТовары", ПоказыватьТовары);
ТабДокумент.Вывести(СформироватьТабличныйДокумент(ПараметрыОтчета));
ТабДокумент.ФиксацияСлева = 4;
КонецПроцедуры
&НаСервере
Функция СформироватьТабличныйДокумент(ПараметрыОтчета)
ТабДок = Новый ТабличныйДокумент;
// какой-то запрос
//какой-то алгоритм заполнения ТабДок
Возврат ТабДок;
КонецФункции
Вариант №2.
Давайте назначим ему права «только чтение»! Ну и порежем интерфейс… Проблемы те же (ну разве что базу не поломает кривыми руками). И добавляется еще невозможность печати и сохранения отчетов.
Код пока не меняется
Вариант №3.
Давайте перепишем права! Если политика позволяет так сделать, то все хорошо. Дописываем права в старшей программе, и пользователь не влезет, и, даже не увидит, что там есть лишнего. Но он при этом продолжает работать в двух базах, а еще у нас теперь переписана старшая база и программист перед очередным обновлением говорит нам за это "спасибо."
Вариант №4.
А что если вообще не пускать пользователя в бухгалтерию и вытаскивать данные из старшей программы по com соединению?
Хороший вариант. Чтобы его реализовать, необходимо перенести из старшей программы код отчетов, переписать все запросы и убедиться, что туда-сюда в составе данных «летают» не ссылки, а примитивные типы. Теперь операторы довольны окончательно! Ведь работают они в одной базе.
Однако программист, обновляющий старшую базу, начинает получать странные сообщения от пользователей других баз: «Вы там что-то вчера делали ночью на сервере, а у нас отчеты не работают!». Странные они, потому что обновляет он одну базу, а ошибки начинаются в другой. Но эти сообщения перестают быть странными, как только программист вспоминает, что полгода назад что-то такое он делал с отчетами в других базах. Но отладить теперь эти отчеты становится трудно, так как все, «что было в Вегасе, остается в Вегасе». А на стороне отладки просто ошибка где-то с чем-то.
Вариант №5.
И в этот момент появляется необходимость хранить алгоритмы, макеты и прочие данные на стороне старшей программы, а на стороне остальных получать только результат. Ну и параметры отправлять на старте.
Собственно, на этом эволюционном шаге мы подходим к необходимости единожды привести к стандарту все отчеты на стороне старшей программы, учесть передачу в составе параметров только примитивных типов и стандартизировать получение отчетов принимающей стороной.
Формируется набор заповедей:
Не создай пользователю второй базы!
Храни запросы и алгоритмы на стороне старшей базы!
Не нажми кнопку "включить возможность изменения"!
Не дублируй код!
Не передай данных без проверок!
Не оставь пользователя без подробностей ошибки!
Чек лист, или порядок действий:
- Создаем расширение на стороне старшей программы.
- Закидываем в нее необходимые внешние отчеты и обработки (не забываем менять «отчет.» на «объект.»;
- Создаем общий модуль «сервер» и «внешнее соединение», через который будем «держать связь»;
- Предусматриваем всевозможные ошибки и делаем какую-никакую обратную связь (см. ниже про ошибки);
- На стороне остальных программ организуем универсальное подключение с передачей всех параметров и «встречу» готовых печатных форм.
С учетом того, что «клиентом» каждого отчета у нас теперь являются 2 базы, необходимо устранить разночтения. Для этого исходные параметры отчета, полученные в виде кодов, наименований и других идентификаторов справочников и документов, в отдельной процедуре в единой функции (например, «ПреобразоватьПараметрыОтчета(ВходящиеПараметры)») нужно преобразовать перед выполнением алгоритма в обычные ссылочные типы и передать в типовую процедуру на стороне старшей программы. Так отладку будет легче делать. И все алгоритмы будут в одном месте.
Что касаемо обратной связи по ошибкам, предлагаю, раз уж мы пользователю возвращаем готовую печатную форму в случае успеха, возвращать печатную форму с описанием ошибок, чтобы на стороне клиента уже не заморачиваться с расшифровкой ошибок (опять попахивает сложностью отладки). Кстати, функция для этого у нас уже имеется. Можно вместо обработанного набора параметров возвращать текст ошибки.
В результате, независимо от источника запроса, алгоритм сбора данных и формирования отчета будет храниться на стороне старшей программы, и отладка его будет происходить в рамках структуры данных этой же программы.
Теперь перейдем к сторонней программе. Мы договорились, что будем заполнять параметры примитивными типами, что, по-хорошему, необходимо реализовать исходя из настроек синхронизации. Ну, или как проще, если мы в Time-to-market и «итеративная поставка».
Так же необходимо реализовать само подключение. Если есть отдельный пользователь для удаленного подключения к старшей базе, то лучше жестко прописать его данные и вообще не показывать пользователю, затем организовать подключение на стороне сервера и #std642. Если авторизация средствами ОС и база на сервере, то подключение на стороне клиента и #std755.
При такой схеме работы, данные хранятся в старшей базе. Алгоритмы, собирающие их и формирующие готовые отчеты, хранятся там же. Доступ регулируется отдельно и администрируется проще (тут есть, где ломать копья). Пользователи работают в своих базах, программисты не распыляются, решая ошибки на месте.
В модуле формы отчета строка:
ТабДокумент.Вывести(СформироватьТабличныйДокумент(ПараметрыОтчета));
Меняется на:
ТабДокумент.Вывести(ДВП_МодульВнешнегоСоединения.СформироватьТабличныйДокумент(ПараметрыОтчета));
Функция СформироватьТабличныйДокумент, как видно, переносится в общий модуль.
Там же создаем функцию СформироватьТабличныйДокументИзВнешнейБазы
Функция СформироватьТабличныйДокументИзВнешнейБазы(ВнешниеПараметрыОтчета) Экспорт
ПараметрыОтчета = ПреобразоватьПараметрыОтчета(ВнешниеПараметрыОтчета);
Если ПараметрыОтчета.Свойство("Ошибка") Тогда
ТабДок = СформироватьПустойТабличныйДокумент(ПараметрыОтчета.Ошибка);
Иначе
ТабДок = СформироватьТабличныйДокумент(ПараметрыОтчета);
КонецЕсли;
Если ВнешниеПараметрыОтчета.Свойство("ЭтоВнешняяБаза") тогда
Возврат ЗначениеВСтрокуВнутр(ТабДок);
Иначе
Возврат ТабДок;
КонецЕсли;
КонецФункции
Она преобразует параметры из примитивных типов и продолжает наш старый алгоритм, если все хорошо.
Код функции преобразования:
Функция ПреобразоватьПараметрыОтчета(ВнешниеПараметрыОтчета)
ТекстОшибки = "";
Отказ = Ложь;
Если не ВнешниеПараметрыОтчета.Свойство("НачалоПериода") Тогда
ТекстОшибки = ТекстОшибки + Символы.ПС +
"Начало периода не указано!";
Отказ = Истина;
КонецЕсли;
Если не ВнешниеПараметрыОтчета.Свойство("КонецПериода") Тогда
ТекстОшибки = ТекстОшибки + Символы.ПС +
"Конец периода не указан!";
Отказ = Истина;
КонецЕсли;
Если не ВнешниеПараметрыОтчета.Свойство("ОрганизацияИНН") Тогда
ТекстОшибки = ТекстОшибки + Символы.ПС +
"ИНН организации не указан!";
Отказ = Истина;
КонецЕсли;
Если не ВнешниеПараметрыОтчета.Свойство("ОрганизацияКПП") Тогда
ТекстОшибки = ТекстОшибки + Символы.ПС +
"КПП организации не указано!";
Отказ = Истина;
КонецЕсли;
Если не ВнешниеПараметрыОтчета.Свойство("ВидРМ") Тогда
ТекстОшибки = ТекстОшибки + Символы.ПС +
"Вид номенклатуры не указано!";
Отказ = Истина;
КонецЕсли;
Если не ВнешниеПараметрыОтчета.Свойство("ПоказыватьТовары") Тогда
ТекстОшибки = ТекстОшибки + Символы.ПС +
"Значение флага *показывать товары* не указано!";
Отказ = Истина;
КонецЕсли;
ПараметрыОтчета = Новый Структура;
Если Отказ Тогда
ПараметрыОтчета.Вставить("Ошибка", ТекстОшибки);
Возврат ПараметрыОтчета;
КонецЕсли;
Организация = ОпределитьОрганизацию(ВнешниеПараметрыОтчета.ОрганизацияИНН, ВнешниеПараметрыОтчета.ОрганизацияКПП);
Если Организация = Неопределено тогда
ТекстОшибки = ТекстОшибки + Символы.ПС +
"Организация не найдена!";
Отказ = Истина;
КонецЕсли;
ВидРМ = Справочники.ВидыНоменклатуры.НайтиПоНаименованию(ВнешниеПараметрыОтчета.ВидРМ);
Если не ЗначениеЗаполнено(ВидРМ) тогда
ТекстОшибки = ТекстОшибки + Символы.ПС +
"Вид номенклатуры не найден!";
Отказ = Истина;
КонецЕсли;
Если Отказ Тогда
ПараметрыОтчета.Вставить("Ошибка", ТекстОшибки);
Возврат ПараметрыОтчета;
КонецЕсли;
ПараметрыОтчета.Вставить("НачалоПериода", ВнешниеПараметрыОтчета.НачалоПериода);
ПараметрыОтчета.Вставить("КонецПериода", КонецДня(ВнешниеПараметрыОтчета.КонецПериода));
ПараметрыОтчета.Вставить("Организация", Организация);
ПараметрыОтчета.Вставить("ВидРМ", ВидРМ);
ПараметрыОтчета.Вставить("ПоказыватьТовары", ВнешниеПараметрыОтчета.ПоказыватьТовары);
Возврат ПараметрыОтчета;
КонецФункции
Функция может вернуть единственный параметр, «ошибка», наличие которого инициирует ветку обратной связи по ошибкам
Код:
Функция СформироватьПустойТабличныйДокумент(ТекстСообщения) ЭКСПОРТ
ТабДок = Новый ТабличныйДокумент;
Макет = ПолучитьОбщийМакет("ДВП_МакетСообщение");
ОбластьШапки = Макет.ПолучитьОбласть("Сообщение");
ОбластьШапки.Параметры.ТекстСообщения = ТекстСообщения;
ТабДок.Вывести(ОбластьШапки);
Возврат ТабДок;
КонецФункции
Важное дополнение! Мы договорились, что обратно будут передаваться готовые печатные формы, но они не передаются! Есть команда ЗначениеВСтрокуВнутр. Тогда в базу пользователя «полетит» текст.
Теперь посмотрим, какой код на стороне сторонней базы с оператором на той стороне монитора.
Если подключение производится на стороне сервера, тогда нам необходимо только правильно сформировать структуру параметров:
&НаСервере
Функция ПолучитьПараметрыОтчета()
ПараметрыОтчета = Новый Структура;
ПараметрыОтчета.Вставить("НачалоПериода", Объект.НачалоПериода);
ПараметрыОтчета.Вставить("КонецПериода", КонецДня(Объект.КонецПериода));
ПараметрыОтчета.Вставить("ОрганизацияИНН", Объект.Организация.ИНН);
ПараметрыОтчета.Вставить("ОрганизацияКПП" , Объект.Организация.КПП);
ПараметрыОтчета.Вставить("ВидРМ", Объект.ВидРМ.Наименование);
ПараметрыОтчета.Вставить("ПоказыватьТовары", ПоказыватьТовары);
ПараметрыОтчета.Вставить("ЭтоВнешняяБаза", Истина);
Возврат ПараметрыОтчета;
КонецФункции
Затем, в процедуре формирующей отчет (ВывестиТабличныйДокумент()) заменить строку, отвечающую за вызов функции формирования табличного документа на:
ТабДокумент.Вывести(ЗначениеИзСтрокиВнутр(Подключение.ДВП_МодульВнешнегоСоединения.СформироватьТабличныйДокументИзВнешнейБазы(ПараметрыОтчета)));
Видим, что добавилась переменная «Подключение». Она и будет содержать настройки подключения и выполнять его:
СтрокаПодключения = ПолучитьСтрокуПодключения();
КомКоннектор = Новый COMОбъект("V83.ComConnector.1");
Попытка
БазаБП = КомКоннектор.Connect(СтрокаПодключения);
Возврат БазаБП;
Исключение
ОбщегоНазначения.СообщитьПользователю(ОписаниеОшибки());
КонецПопытки;
Возврат неопределено;
СтрокаПодключения это переменная, содержащая параметры подключения. Там все банально. Помним только, что если авторизация средствами ОС, то передавать в ней имя пользователя и пароль не надо.
Кстати, если подключение выполняется на стороне сервера, то пользователем будет sa, userV8 или под кем там запущен сервер 1С. Если надо доменного пользователя, тогда подключение необходимо делать на стороне клиента:
&НаКлиенте
Функция ПодключитьсяКБазеБухгалтерии()
СтрокаПодключения = ПолучитьСтрокуПодключения();
КомКоннектор = Новый COMОбъект("V83.ComConnector.1");
Попытка
БазаБП = КомКоннектор.Connect(СтрокаПодключения);
Возврат БазаБП;
Исключение
ОбщегоНазначенияКлиент.СообщитьПользователю(ОписаниеОшибки());
КонецПопытки;
Возврат Неопределено;
КонецФункции
В качестве бонуса, пример отчета, вытаскивающего данные о продажах через розничные точки с разбивкой на наше/не наше.
Расширение тестировалось на 1С:Бухгалтерия 3.0.128.35. обработка на любой базе с БСП на платформе 8.3.21.1644.