Есть устоявшийся миф о том, что печатные формы – это что-то второстепенное и несущественное. Но несмотря на развитие электронного документооборота, печатные формы до сих пор остаются единственным официальным документом, на котором мы ставим подписи и печати – неважно, в электронном или в бумажном виде.
Более того, я как бывший бухгалтер не могу не упомянуть, что документы – это основание для отражения фактов хозяйственной жизни. По документам, которые вы сделали, вам либо заплатят, либо предъявят претензии. И это до сих пор самое важное, что мы имеем.
Даже если мы передаем документы в формате XML, конечным пользователям не интересны теги – им нужна привычная и понятная печатная форма. Я считаю, что этого достаточно, чтобы убедить вас, что печатные формы достаточно важны.
И хотя мы как программисты относимся к разработке печатных форм пренебрежительно – мы же сеньоры, зачем нам это все, пускай джуны пишут – такое отношение потом приводит к серьезным проблемам. Я чуть позже покажу, что имею в виду.
Позволю себе краткую историческую справку.
Для меня век 1С начался с 7.7, которая вышла в 1999 году.
Там было все великолепно, особенно «продуманными» были отчеты и печатные формы – для их написания нужно было много спагетти-кода.
Да, у разработчиков были 1С++ и конструктор Берездецкого, но по большому счету – это был кошмар, боль и страдания.
В 2003 году вышла 8.0 – для печатных форм ничего не изменилось.
Да, появились SQL-подобные запросы и построитель отчета – возможно, кто-то его помнит. Это была маленькая революция – наконец-то мы могли делать отчеты по-другому. Но печатные формы мы продолжали делать точно так же.
В 2006 году выходит 8.1 с системой компоновки данных (СКД), которая полностью изменила подход к работе с отчетами – к тому, как их можно доносить до пользователей, как их можно менять.
Мы впервые увидели полноценную систему, одинаково работающую и в конфигураторе и в режиме 1С:Предприятия. Но с печатными формами – все опять то же самое.
В 2009 году вышла 8.2. Примерно в это время я закончил развивать все свои классы для 1С++7.7 и полностью ушел в «восьмерку».
В 8.2 использовалась абсолютно новая парадигма управляемых форм, построенная на клиент-серверной архитектуре. Кто-то ее осознал сразу, а кто-то – намного позже.
Самое интересное – формы стали декларативными, мы теперь можем их просто описать в определенном формате. Да, мы еще не увидели их XML-структуру, но те, кто сразу полез под капот, узнали о том, что существует сериализация, которая передает данные формы между клиент и сервером.
Так или иначе, многим из нас стало понятны слова «контекст» и «сериализация». Главное здесь – сериализация, она потом мне тоже пригодится.
Кстати, многие программисты, которые пришли примерно в это время, вообще не знают, что там было до управляемых форм, до клиент-серверной архитектуры. Я им даже немного завидую, они счастливчики.
И последний актуальный этап – 8.3. Да, уже есть 8.5, но она больше про интерфейс, а нам пока не до него.
Главные две вещи, которые произошли в 8.3:
-
Первое – нам выкатили модель схемы запроса. Это классная штука для тех, кто научился ей пользоваться. Мы привыкли, что запрос – это текст, а тут нам дали модельку, которую можно визуализировать и в конечном итоге даже разложить в какое-то дерево, чтобы пользователь понял, откуда что куда подтянулось и как сложилось.
-
А второе – у нас динамические списки перешли на СКД.
Почему схема запроса так важна? Когда я изучал, что происходит под капотом в той форме, которая называется «Конструктор запросов», мне стало страшно представить, что там происходило до появления схемы запросов. Наверное, это был просто кошмар.
Тем не менее сквозняком через все это проходит одно – что с печатными формами за это время не изменилось ничего. Все как было, так и осталось.
Хотя тут я, наверное, немного соврал.
У нас есть божественный конструктор печати – его даже в EDT перетащили, я инструкцию по нему нашел, прочитал, всплакнул.
Есть возможность конструировать печатные формы в БСП 3.1.6. Можно даже создавать новые, если они не слишком сложные. На сайте ИТС есть инструкция – как разрабатывать печатные формы в конфигураторе, чтобы их было удобно менять в режиме 1С:Предприятия.
Я попробовал, но мне пока не очень понравилось, хотя что-то уже можно, и это интересно.
Главная проблема – БСП все-таки не позволяет в разработанных печатных формах что-то активно добавлять или убавлять в режиме 1С:Предприятия. Поразукрашивать – пожалуйста. Но только это мало кому надо.
И да, на стороне 1С:Предприятия есть еще третий вариант – это конструкторы. В том числе, PrintWizard – самый лучший конструктор (по версии британских ученых).
Тем не менее основной способ создания печатной формы остается прежним. На слайде – более-менее приличный пример формирования печатной формы из типовой конфигурации.
В реальности – мы все с вами видели, что происходит во внешних печатных формах. Руки засунул – не испачкался, и слава богу.
Подведем итог – со стороны конфигуратора у нас нет никаких удобств для разработки печатных форм.
-
Разве что у программистов есть определенные паттерны и предложения со стороны БСП. Плюс некоторые компании разрабатывают свои подходы к разработке печатных форм – у меня в компании тоже это есть, и я это активно продвигаю.
-
Но со стороны 1С:Предприятия у нас все как в поговорке – что происходит в Вегасе, остается в Вегасе. Можно сделать какие-то конструкторы, но они будут работать только в 1С:Предприятии.
А у конструкторов, которые работают в 1С:Предприятии – другая проблема.
-
К таким конструкторам есть недоверие со стороны разработчиков, и оно в некоторой мере обосновано. Вдруг нам теперь это нужно самим поддерживать? Там же косяки! Как мы должны отвечать, если у пользователей возникнут вопросы?
-
А пользователи тоже опасаются. Они видят, что разработчик не доверяет конструктору, и тоже начинают сомневаться, что это работает.
Поэтому ситуация получается патовая: в конфигураторе печатные формы разрабатывать больно, а в 1С:Предприятии – мы опасаемся.
Скажу честно: когда я разместил PrintWizard на Инфостарте, у меня начался период апатии, потому что мне не нравилось то, что я видел под капотом. Снаружи все классно, удобно, здорово (это не мое мнение, так в отзывах пишут), а то, что внутри – мне не нравилось. И я все искал ответ – что же мне может помочь…
И тут до меня дошло, что здесь мне может помочь MVP. Если кто знает, на слайде – обладатель награды MVP 2021 года, вратарь клуба «Тампа-Бэй Лайтнинг» Андрей Василевский.
Но у аббревиатуры MVP есть и другое значение.
Те, кто знаком с паттерном разработки веб-приложений MVC (Model-View-Controller), знают, что следующим этапом развития этого паттерна стал MVP (Model-View-Presenter) – популярный архитектурный шаблон, который используется для построения приложений.
Смысл этого шаблона в разделении ответственности между тремя компонентами:
-
Model (модель) отвечает за бизнес-логику: чтение, запись, обработку данных и взаимодействие с внешними источниками. При этом она вообще никак не зависит от представления – от того, что видит пользователь. У модели него есть некий программный API, через который она выводит данные. Например, в СКД мы можем выбросить конструктор и описать всю схему компоновки данных кодом – это аналог того, что делает модель в шаблоне MVP.
-
View (представление) – это внешний облик диалоговой формы, то, что мы показываем пользователю. Оно отвечает за отображение информации и взаимодействие с пользователем – мы продумываем поведение пользователя в интерфейсе и проводим его за руку до финального результата. Но опять-таки, представление ничего не знает о том, откуда приходят данные и как они обрабатываются – оно просто делает красиво и хорошо.
-
Presenter (по-русски его можно назвать ведущий или дирижер) – это связующее звено между моделью и представлением. Когда пользователь что-то меняет на форме, Presenter перерабатывает эту информацию в определенную структуру, соответствующую контракту взаимодействия с моделью, и отдает это модели.
Яркий пример этой концепции – это обработка, которая реализует конструктор запросов в 1С:Предприятии:
-
View – это сам интерфейс конструктора, который мы видим.
-
Presenter отвечает за обработку наших действий в конструкторе и реагирует на кнопки, которые мы нажимаем.
-
А Model – это схема запроса.
И если так подумать, в контексте того же PrintWizard в принципе все то же самое происходило.
-
Там есть внешний вид – т.е. представление,
-
Там есть ведущий, который обрабатывает клики и показывает дополнительные формы – создает все это удобство и красоту,
-
Но с моделью возникла некоторая проблема – она у меня была размыта по всему коду, и ярко выраженной не было.
В связи с этим, собственно, и возник доклад.
Я пришел к выводу, что для PrintWizard нужны три элемента:
-
Объектная модель, которая будет отвечать за бизнес-логику; предоставлять данные, которые потом будут использованы в представлении; и самое главное – решать задачу перехода состояний и трансформации сущностей из формата конфигуратора в формат 1С:Предприятия. Потому что сейчас мы можем использовать PrintWizard только в режиме 1С:Предприятия, но в конфигуратор он ничего не возвращает. В контексте 1С эта объектная модель должна быть реализована в виде обработки, работающей в 1С:Предприятии и предоставляющей API для декларативного описания печатной формы в виде кода. Чуть попозже вы это все увидите.
-
Ключевая часть этой обработки должна отвечать за сериализацию и решать задачу перехода состояний, предоставляя возможность формировать эту печатную форму в декларативном описательном виде через XML со структурой, жестко заданной в XSD-схеме. В эту структуру записывается готовый результат.
-
И, конечно, третий недостающий элемент – это исполнитель, который способен принять этот XML, и, используя объектную модель, выдать нам готовый результат.
-
Понятно, что где-то рядом с этим красной нитью проходит СКД, но она на самом деле работает точно так же – там все то же самое, только дополнительно еще есть ПроцессорВывода. В моей модели он пока отсутствует, потому что в PrintWizard пока что можно на выбор формировать только печатные формы для табличного и для офисного документа. Но объективно – процессор вывода может появиться, потому что дополнительно может появиться необходимость вывода в форматированный XML – например, для обмена с какими-то другими системами. Почему бы и нет? Мы берем конструктор и строим XML – какая нам разница, что по большому счету строить?
Чтобы лучше понять устройство объектной модели, давайте кратко расскажу, как мы разрабатываем печатную форму.
Сразу оговорюсь, что большинство идей рождаются не потому, что их создателю «яблоко на голову упало», а потому что мы начинаем замечать в обычных действиях какие-то закономерности, которые раньше не замечали. Часто решение буквально лежит под ногами, а мы не обращаем на него внимания. То же самое произошло и с созданием конструктора для печатных формам.
В идеале разработка печатной формы должна начинаться с анализа образца – нам дают описание печатной формы с полями и объяснением, как эти поля заполнять. В результате аналитик делает из этого шаблон и раскладывает его на составные части:
-
указывает, что выводится в шапке;
-
каким должен быть заголовок;
-
какие строки нужно повторить для вывода данных по табличной части;
-
что выводится в подвал;
-
какими должны быть подписи.
Если вдуматься, большинство печатных форм так и устроено: заголовок, табличная часть, подвал, подписи.
Однако на дальнейших этапах разработки печатной формы происходит многократное дублирование одних и тех же действий:
-
сначала аналитик накидывает структуру, а потом программист все это перерисовывает;
-
сначала аналитик рассказывает, где взять данные, потом программист прописывает в программе, где взять данные и т.д.
На слайде скриншоты из PrintWizard – показано, каким образом мы выделяем из табличного макета области:
-
некоторые области нам надо выводить в шапке;
-
некоторые – в подвале;
-
некоторые повторяются потому, что у них источник – табличная часть, и по каждой строке надо повторить эту область.
И из этих областей мы получаем параметры, которые нужно заполнить.
После того как мы визуально разметили, что нужно заполнить, мы должны указать, откуда будем заполнять – у нас появляются некоторые наборы.
Понятие набора – достаточно абстрактное. Источником данных для набора могут быть:
-
объекты метаданных;
-
произвольные запросы;
-
внешние источники – мы можем получить данные по REST API;
-
можем обратиться напрямую к Excel или к любой базе данных;
-
или просто наполнить кодом таблицу значений и обратиться к ней;
Главное, что дают нам наборы – это поля, которые мы можем прилинковать в нашей печатной форме.
Кроме того, печатная форма, как правило, формируется для конкретного объекта. А чтобы ее подключить к объекту, нужно создать и настроить команду печати – это уже ближе к требованиям БСП. В PrintWizard это все тоже можно настроить.
Если собрать всю эту информацию по печатной форме вместе, можно нарисовать такую схему:
-
у нас есть макет;
-
у макета есть шаблон;
-
в шаблоне выделены некоторые области;
-
с другой стороны, у нас есть наборы;
-
они формируются из запросов;
-
наборы можно между собой соединять;
-
на основании наборов и их соединений мы получаем определенный набор полей;
-
кроме этого, есть понятие доступные поля – например, мы не можем в одну ячейку вывести табличную часть целиком, доступность поля определяется в зависимости от областей;
-
в итоге все это сводится к параметрам – доступные поля с параметрами линкуются;
-
и отдельно там наверху указаны события – это некая возможность вмешаться в процесс.
Тут я хотел закопаться еще глубже, но решил, что душнить все-таки не буду, но про код попробую вам немного рассказать.
На слайде – рабочий прототип кода для управления объектной моделью печатной формы:
-
мы получаем макет;
-
создаем схему печатной формы (по аналогии с тем, как мы создаем схему компоновки данных);
-
и устанавливаем макет в качестве шаблона.
Дальше программа сама все это разберет и выдаст нам в результате вот такой XML, в котором есть:
-
узел макета – Template;
-
отдельные узлы под области – Areas;
-
и в них вложены параметры – Parameters.
Все это программа самостоятельно считывает, понимая, что тут есть макет, области и параметры в них.
Следующим этапом мы добавляем запросы.
На слайде – стандартный запрос: мы получаем реквизиты шапки из документа.
И последняя строчка – мы добавляем текст запроса и просто даем ему некоторое имя, потому что нам проще работать с именами, чем с какими-то ключами.
В результате программа формирует некий источник данных – складывает его к себе в XML-формате, запоминает и раскладывает на поля. На слайде видно, что в XML хранится информация о ключах, именах и типах каждого поля.
Дальше мы на основании этих запросов (или на основании других источников) формируем наборы. На слайде показан код, с помощью которого мы это делаем – добавили запрос, и на основании него формируем набор.
Напомню, что области в табличном документе могут повторяться – если мы печатаем табличную часть, мы по сути одну и ту же область для каждой строки выводим. Поэтому мы формируем наборы, которые далее будем привязывать к области:
-
первый набор нам нужен для шапки;
-
второй набор – это товары, он многострочный.
Причем при формировании шапки мы привязываем к области только первую строку набора (указываем, что ВидНабора_ПерваяСтрока). Это важно, потому что у нас может быть пакетная печать, при которой мы получаем таблицу значений сразу под несколько документов.
И после этого мы даем программе команду обновить доступные поля, чтобы она пересмотрела структуру печатной формы.
В результате в нашем XML уже появляются узлы:
-
Datasets – это наборы;
-
они содержат Fields – поля;
-
и Source – описанный источник.
Почему нужен отдельно описанный источник? Потому что источником, как я уже говорил, может выступать что угодно – СКД, таблица значений, соединение с базой данных и т.д. Любое, что нам в принципе может понадобиться.
И в конечном итоге мы получаем некоторые доступные поля. У каждого из них есть:
-
имя – допустим, Наборы.Шапка.Ссылка;
-
тип;
-
и другие дополнительные реквизиты, которые нужны программе для связывания данных между собой – я чуть попозже это покажу.
В печатных формах часто встречаются ситуации, когда мы не можем вставить просто название контрагента – нам надо взять его название, ИНН, КПП и добавить туда адрес. А получить адрес – это тоже своеобразная задача. Поэтому в PrintWizard есть понятие дополнительных полей.
С помощью дополнительных полей в программе можно быстро получить юридический адрес и другие поля, а потом собрать это в какое-то представление.
Кроме этого, с их помощью можно задать использование собственного нумератора или вывести сумму прописью.
Кто-нибудь помнит, как в БСП вызвать сумму прописью? В каком общем модуле это лежит? Любимая задачка.
В итоге это тоже все отражается в XML, который добавляет в набор поле с тегом Value, где лежит описание, как получить данные для этого поля.
Далее мы указываем дополнительные свойства областей:
-
какие – вообще пропустить, не выводить на печать;
-
какие – связать с коллекцией товары.
И в конечном итоге это все опять-таки отражается в XML.
И на финальном этапе происходит линковка данных:
-
между параметрами, объявленными в шаблоне;
-
и источниками – откуда из доступных полей это можно взять.
Здесь есть возможность:
-
установить формат для полей,
-
указать формулы для вычисления (по аналогии тем, как в СКД указываются формулы для ресурсов) – например, Сумма(), Среднее() и т.д.
В итоге эта связь параметров и полей рождает связи данных внутри схемы.
-
какой набор связан с каким запросом;
-
какой набор использовался в какой области;
-
какое поле использовалось в каком параметре;
-
как параметры связаны с областью.
В результате мы можем наглядно видеть, что откуда было в печатной форме взято.
Большинство спагетти-кода в печатной форме выглядит так – мы ищем место, где заполняется поле, видим, что там вызывается функция, провалились в функцию и уже забыли, откуда начали.
Если кто-то знает, что обратно можно вернуться через Ctrl+минус, слава богу, вернется. Я сам не так давно узнал, что есть такая горячая клавиша. До этого полжизни страдал: провалился куда-то и потерялся.
Ну и в конечном итоге – Исполнитель. Здесь демонстрируется, что XML-схема, которая создана на стороне 1С:Предприятия, может совершенно спокойно применяться в конфигураторе.
-
Первая строка – это мы получаем из XML схему. Наверняка вы сталкивались с конструкторами, которые из XML-схемы СКД строят код для формирования такого СКД – здесь, по большому счету, все то же самое.
-
Далее вызывается Исполнитель.
-
Он загружает схему.
-
И вызывается магическая команда «Печать()», которая возвращает результат.
Понятно, что пытливый читатель увидит в этой печатной форме дырки.
Честно признаться, я боялся, что вообще не соберу этот рабочий прототип. Но я его собрал, и он заработал! Да, там по пути какая-то часть потерялась, но я его просто еще не успел отладить до конца.
Причем это уже не просто теория, а опытно-промышленный экземпляр, который пока что проходит обкатку.
Картинка на слайде показывает мои ощущения – вроде бы все уже заработало, но я пока от шока еще не отошел.
В конечном итоге все это выражено в трех объектах.
-
Обработка pw_Схема – у нее есть свой набор реквизитов, своя объектная модель под капотом, то есть API, которым можно пользоваться.
-
XSD-пакет, который можно посмотреть, понять, как он сложен, из чего он состоит.
-
И совершенно маленький скромненький Исполнитель – он скромненький только потому, что у него нет механизма отладки. Когда такой механизм отладки появится, мы сможем создавать печатную форму поэтапно – пошагово смотреть, как там формируются наборы, какие данные там и так далее.
-
Плюс там еще десяток общих модулей рядом валяется, но я их скринить не стал.
*************
Послесловие от автора
Это реклама PrintWizard? И “да” и “нет”. Почему “да” - тут все просто, он упоминается в докладе, скрины взяты из него, и все разработки так или иначе существуют в рамках этого продукта. Почему “нет” - предложенная схема формирования печатных форм планируется доступной для сообщества. То есть будет возможность скачать необходимый инструментарий, добавить к себе в конфигурацию и создавать печатные формы используя предложенную модель.
Следите за новостями.
*************
Статья написана по итогам доклада (видео), прочитанного на конференции INFOSTART TEAMLEAD & CIO EVENT.
Вступайте в нашу телеграмм-группу Инфостарт