Pull to refresh

Генератор отчетов ActivityManager. Очередной велосипед, но в профиль

Reading time46 min
Views9.5K
ActivityManager — это менеджер формирования отчетов, базирующийся на замене шаблонных строк.
Основными особенностями ActivityManager являются:

  • Независимость от источника данных: поддерживаются все СУБД, для которых существуют провайдеры .Net, и не только;
  • Формирование шаблонов без использования COM: все отчеты формируются непосредственно в XML;
  • Поддержка форматов отчетов ods, odt, docx, xlsx. Независимость от наличия текстового процессора на конечном компьютере пользователя: эта особенность вытекает из предыдущей;
  • Наличие механизмов пред-обработки данных: изменение формата представления ФИО, денежных сумм, целых, вещественных чисел и дат, в том числе и возможность изменения падежа, в котором должны быть представлены конечные данные;
  • Наличие механизмов пост-форматирования данных;
  • Простота использования и расширения благодаря наличию редактора конфигурации отчетов и простой плагинной архитектуре.

Конфигурацию отчета при помощи ActivityManager можно условно разделить на 3 части: выборка данных, их обработка и непосредственно формирование отчета. Подробную информацию по каждому из этапов смотри в соответствующих разделах.

Глоссарий


  • Файл шаблона — файл формата odt, ods, docx или xlsx, служащий в качестве заготовки для будущего отчета
  • Шаблонная строка — элемент строки вида $variable$ в файле шаблона, который при формировании отчета будет заменен на подставляемые данные
  • Плагин — обычная сборка .Net, реализующая специальный интерфейс IPlug.
  • Действие (action) — с технической точки зрения это публичный метод класса, реализующего интерфейс IPlug и видимый через этот интерфейс
  • Шаг (step) — этап исполнения последовательности действий, описанный в конфигурационном файле отчета. Шаг отличается от действия тем, что действие — это описание сигнатуры того, что необходимо сделать на данном шаге, а шаг — это оболочка, связывающая сигнатуру с конкретными значениями параметров

Структура файла конфигурации


Файл конфигурации отчета представляет собой обычный xml-файл следующего вида:

Пример
<?xml version="1.0" encoding="utf-8"?>
<activity>
    <plugins>
        <include>ConvertModule.dll</include>
        ...
    </plugins>
    <language>ru</language>
    <step plugin="SqlDataSource" action="SqlSetConnectionString" repeat="1">
        <input>
            <parameter name="connectionString">dsn=registry</parameter>
        </input>
    </step>
    <step plugin="SqlDataSource" action="SqlSelectTable" repeat="1">
        <input>
            <parameter name="query">SELECT * FROM executors;</parameter>
        </input>
        <output>
            <parameter name="table">new_table</parameter>
        </output>
    </step>
    ...
</activity>


Основную часть конфигурационного файла отчета занимают шаги. Шаги по сути представляют этапы формирования отчета: выборка данных, обработка данных, конфигурация отчета, источника данных и многое другое. Шаг состоит из имени плагина plugin, в котором находится действие action, которое необходимо выполнить, входных <input/> и выходных <output/> параметров и параметра repeat, указывающего сколько раз необходимо выполнить данный шаг (по умолчанию 1). Каждый входной параметр имеет имя name и значение (контент). Каждый выходной параметр имеет имя по умолчанию name< и переопределение имени (контент). По этому имени параметра на последующих шагах формирования отчета можно получить его значение из коллекции глобальных параметров. Подробнее про коллекцию глобальных параметров смотри в разделе "Передача параметров"

В верхней части файла конфигурация находится блок <plugins/>. В данном блоке расположен перечень загружаемых плагинов во время исполнения. Плагины по умолчанию расположены в каталоге plugins\. Блок <language/> указывает язык, на котором будут выводиться сообщения (по большей части об ошибках) во время формирования отчета. На данный момент поддерживается два языка: русский и английский. Языковые переводы расположены в каталоге lang\.
Несмотря на простую структуру файла конфигурации редактировать его вручную утомительно. Поэтому был разработан специальный редактор файлов конфигураций. Подробнее в разделе "Визуальный редактор файлов"

Поставляемые плагины


В поставке по умолчанию ActivityManager присутствуют 6 базовых и один custom-плагин:

  • SqlDataSource.dll — плагин доступа к источникам данных SQL;
  • TextDataSource.dll — плагин доступа к источникам данных CSV через SQL;
  • ConvertModule.dll — Плагин преобразования данных. В этом плагине находятся действия преобразования формата представления данных, падежей, действия объединения в единую строку отчета целых таблиц, столбцов и строк, а также выбора отдельных строк, ячеек из таблицы источника данных;
  • ReportModule.dll — непосредственно сам плагин формирования отчетов;
  • IOModule.dll — данный плагин предназначен для операций ввода/вывода и управления порядком исполнения шагов. В данном модуле находятся действия запуска сформированного отчета в текстовом процессоре (и любого другого файла или приложения), вывода отладочной информации на консоль и в MessageBox, действия условного перехода, благодаря которым становится возможно делать в отчете ветвления и циклы (подробнее об этом смотрите в разделе "Действия условного перенаправления и JS макросы");
  • JSModule.dll — данный плагин предназначен для написание достаточно простых JavaScript-макросов. Первоначальное назначение данного плагина — вычисление условий переходов, реализованных в модуле IOModule.dll. Но данным функционалом этот плагин конечно же не ограничен;
  • MenaModule.dll — это custom-модуль, который был разработан как специфичное расширение функционала для конкретной программы по мене жилья в нашей организации. Плагин демонстрирует, как легко можно добавить в генератор отчета свою функциональность, если базовой не хватает для решения поставленных задач.

Передача параметров


Параметры командной строки


При запуске генерации отчета ядру (ActivityManager.exe) необходимо передать один обязательный параметр config в виде

ActivityManager.exe config="c:\1.xml"

В результате ActivityManager.exe прочитает файл конфигурации, расположенный по пути c:\1.xml и сформирует отчет.
Помимо обязательного параметра config ActivityManager'у можно передать параметр lang — язык выводимых во время исполнения сообщений (по большей части ошибок, возникших при формировании отчета).

Кроме базовых параметров config и lang ActivityManager может принимать любое количество любых других параметров, которые будут помещены в коллекцию глобальных параметров времени исполнения и могут быть использованы во время формирования отчета. Например:

ActivityManager.exe config="c:\1.xml" connectionString="dsn=registry" id_process=318

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

Глобальные параметры времени исполнения


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

  • При передаче в командной строке (см. предыдущий пункт)
  • Как результат исполнения предыдущих шагов формирования отчета.

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

ActivityManager.exe config="c:\1.xml" connectionString="dsn=registry" id_process=318

Тогда во время исполнения приложения мы можем использовать переданные аргументы командной строки следующим образом:

Пример
<step plugin="SqlDataSource" action="SqlSetConnectionString" repeat="1">
    <input>
        <parameter name="connectionString">[connectionString]</parameter>
    </input>
</step>
<step plugin="SqlDataSource" action="SqlSelectTable" repeat="1">
    <input>
        <parameter name="query">SELECT * FROM tenancy_processes WHERE id_process = [id_process]</parameter>
    </input>
    <output>
        <parameter name="table">tenancy_processes</parameter>
    </output>
</step>
<step plugin="ReportModule" action="ReportSetTableValue" repeat="1">
    <input>
        <parameter name="table">[tenancy_processes]</parameter>
        <parameter name="xmlContractor">Row</parameter>
    </input>
</step>
<step plugin="ReportModule" action="ReportGenerate" repeat="1">
    <output>
        <parameter name="fileName"></parameter>
    </output>
</step>


Как видно, на первом шаге SqlSetConnectionString (установка строки соединения) во входящем параметре указывается [connectionString], что говорит препроцессору, что необходимо взять этот параметр из коллекции глобальных параметров. В случае, если параметр, находящийся в квадратных скобках не будет найден в коллекции глобальных параметров, никаких сообщений об ошибках не будет сгенерировано. ActivityManager поймет, что данная строка не является параметром и оставит ее без изменений.

Обратите внимание на выходной параметр шага SqlSelectTable

<output>
    <parameter name="table">tenancy_processes</parameter>
</output>

Данная запись означает, что после исполнения запроса данные из источника будут сохранены в глобальной области параметров под именем tenancy_processes и к ним можно будет обратиться через [tenancy_processes] (что и делается на следующем шаге ReportSetTableValue)

<input>
    <parameter name="table">[tenancy_processes]</parameter>
    <parameter name="xmlContractor">Row</parameter>
</input>

Замена параметров простых типов (строки, числа, даты и т.д., а именно всех типов, которые можно однозначно конвертировать из строкового типа System.String в целевой при помощи Convert.ChangeType) осуществляется в режиме подстановки. Т.е. вполне допустимо задавать параметр как часть значения, а не как целое. Например:

<input>
    <parameter name="query">SELECT * FROM tenancy_processes WHERE id_process = [id_process]</parameter>
</input>

Параметры более сложных типов, которые невозможно однозначно конвертировать из строкового представления, будут подставлены непосредственно. В таких параметрах частичная подстановка невозможна по определению.

Подстановка параметров из глобальной коллекции выполняется только во входящих параметрах действий.

Выборка данных


Прежде чем сформировать отчет, необходимо получить данные. На данный момент источниками данных для ActivityManager могут служить СУБД, для которых существует .Net-провайдер, а также текстовые файлы в формате CSV. К сожалению пока нет нативного источника данных для XML.

Результатом выборки данных из любого из описанных источников будет объект либо скалярного типа либо типа ReportTable.

SQL-источники данных


Для работы с источниками данных SQL предназначен плагин SqlDataSource.dll.
Простой примером использования данного плагина для выборки данных из ODBC-источника данных:

Пример
<step plugin="SqlDataSource" action="SqlSetConnectionString" repeat="1">
    <input>
        <parameter name="connectionString">dsn=registry</parameter>
    </input>
</step>
<step plugin="SqlDataSource" action="SqlSelectTable" repeat="1">
    <input>
        <parameter name="query">SELECT * FROM executors;</parameter>
    </input>
    <output>
    <parameter name="table">new_table</parameter>
    </output>
</step>


На первом шаге данного примера устанавливается строка соединения. На втором производится выборка данных из таблицы executors, данные записываются в таблицу с именем new_table. После чего мы можем производить над ними различные модификации через плагин ConvertModule.dll или отправить непосредственно в отчет.
В качестве провайдера по умолчанию в SqlDataSource используется ODBC, но допускается установка любого поддерживаемого в .Net провайдера. Для установки провайдера используется функция SqlSetProvider. Например, вот так можно установить провайдер OLE DB:

<step plugin="SqlDataSource" action="SqlSetProvider" repeat="1">
    <input>
        <parameter name="name">OLE</parameter>
    </input>
</step>

Здесь параметр name — это полное или краткое инвариантное имя провайдера. Нет необходимости задавать полное инвариантное имя, т.к. сопоставление имени происходит нечетко (по паттерну). Например, вместо полного инвариантного имени для MS Sql Server (System.Data.SqlClient) можно указать просто:

<step plugin="SqlDataSource" action="SqlSetProvider" repeat="1">
    <input>
        <parameter name="name">SQL</parameter>
    </input>
</step>

или, например

<step plugin="SqlDataSource" action="SqlSetProvider" repeat="1">
    <input>
        <parameter name="name">SqlClient</parameter>
    </input>
</step>

Полный перечень поддерживаемых провайдеров зависит от машины, на которой происходит генерация отчета и может быть получен через метод DbProviderFactories.GetFactoryClasses().
Помимо действия SqlSetProvider и SqlSelectTable в плагине SqlDataSource имеется еще 8 действий. Полный их перечень можно найти на wiki проекта

CSV-источники данных


Помимо работы с СУБД ActivityManager поддерживает выборку и модификацию данных файлов CSV. Для выборки данных из CSV-файлов помимо Microsoft Text Driver через ODBC поддерживается возможность осуществлять выборку встроенным текстовым драйвером, построенным на базе синтаксиса MySQL.
Например, предположим мы имеем два CSV-файла users.csv и user_actions.csv следующего содержания:
users.csv:

id|surname|name|patronymic
1|Игнатов|Василий|Васильевич
2|Иванов|Иван|Иванович

user_actions.csv:

id_user|action
1|действие1
1|действие2
1|действие3
2|действие4

Нам необходимо выбрать количество действий для каждого пользователя.
Конфигурация шага выборки данных из CSV при этом будет выглядеть следующим образом:

Пример
<step plugin="TextDataSource" action="TextSelectTable" repeat="1">
    <input>
        <parameter name="query">
                SELECT a.surname, a.name, a.patronymic, COUNT(*) AS record_count
                FROM [csv_path]users.csv a
                LEFT JOIN [csv_path]user_actions.csv b ON a.id = b.id_user GROUP BY a.id
        </parameter>
        <parameter name="columnSeparator">|</parameter>
        <parameter name="rowSeparator"></parameter>
        <parameter name="firstRowHeader">true</parameter>
        <parameter name="ignoreDataTypes">true</parameter>
    </input>
    <output>
        <parameter name="table"></parameter>
    </output>
</step>


Результат данного запроса будет сохранен в коллекции глобальных параметров под именем table.
Помимо действия TextSelectTable в плагине TextDataSource имеется еще 2 действия: TextSelectScalar и TextModifyQuery, подробную информацию о которых вы также можете узнать на wiki проекта.

Преобразование данных


Нередко возникает необходимость в форматировании данных уже после выборки из источника и до их записи в конечный отчет. Причин этому может быть множество от отсутствия поддержки на уровне СУБД каких-либо сложных преобразований, до нежелания «марать» SQL-запрос массой излишних условий, превращающих его в нечитабельную простыню.
Для подобных преобразований данных в ActivityManager используется плагин ConvertModule.dll.
Вот перечень того, что можно сделать с данными при помощи этого плагина:

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

Рассмотрим функции преобразования формата данных подробнее. Все функции преобразования данных можно классифицировать

По типу преобразуемых данных:

  • Представления целых чисел: ConvertIntToString, ConvertIntCellToString, ConvertIntColToString;
  • Представления вещественных чисел: ConvertFloatToString, ConvertFloatCellToString, ConvertFloatColToString;
  • Представления даты и времени: ConvertDateTimeToString, ConvertDateTimeCellToString, ConvertDateTimeColToString;
  • Представления денежных сумм: ConvertCurrencyToString, ConvertCurrencyCellToString, ConvertCurrencyColToString;
  • Представления ФИО: ConvertNameToCase, ConvertNameCellToCase, ConvertNameColToCase;

По типу входных и выходных параметров:
  • Преобразования над скалярными величинами: ConvertIntToString, ConvertFloatToString, ConvertDateTimeToString, ConvertCurrencyToString, ConverNameToCase;
  • Преобразования над ячейками объекта типа ReportRow (данный объект является результатом выполнения действия GetRow плагина ConvertModule.dll): ConvertIntCellToString, ConvertFloatCellToString, ConvertDateTimeCellToString, ConvertCurrencyCellToString, ConvertNameCellToCase;
  • Преобразования над столбцами объекта типа ReportTable (данный объект является результатом выполнения действий SqlSelectTable и TextSelectTable плагинов SqlDataSource.dll и TextDataSource.dll соответственно): ConvertIntColToString, ConvertFloatColToString, ConvertDateTimeColToString, ConvertCurrencyColToString, ConvertNameColToCase;

Преобразования целых чисел


Как уже говорилось выше для преобразования целых чисел используются действия ConvertIntToString, ConvertIntCellToString, ConvertIntColToString. Отличаются они между собой только типом входного и выходного параметра поэтому подробно рассматривать каждое из них мы не будем. Рассмотрим простой пример преобразования чисел в текстовое представление. Предположим, что нам необходимо из таблицы базы данных выбрать некоторые числовые значения, после чего привести их к текстовому виду по следующим правилам: все числа должны быть в родительском падеже, окончания текстовых представлений чисел должны быть в женском роде, числа должны быть количественными, а не порядковыми, первая буква результирующих текстовых представлений чисел должна быть большой. Для начала нам необходимо получить данные, которые мы будет преобразовывать. Ранее уже было показано как это делается:

Пример
<step plugin="SqlDataSource" action="SqlSelectTable" repeat="1">
    <input>
        <parameter name="query">
            SELECT 1 AS int_column
            UNION
            SELECT 13013
            UNION
            SELECT 55132111
            UNION
            SELECT 1055132111
        </parameter>
    </input>
    <output>
        <parameter name="table"></parameter>
    </output>
</step>


В результате данного действия в коллекции глобальных объектов будет хранится объект типа ReportTable, содержащий один столбец int_column содержащую строки со значениями 1, 13013, 55132111, 1055132111.

Непосредственно само преобразование по определенным выше требованиям можно провести следующим образом:

Пример
<step plugin="ConvertModule" action="ConvertIntColToString" repeat="1">
    <input>
        <parameter name="inTable">[table]</parameter>
        <parameter name="column">int_column</parameter>
        <parameter name="textCase">Genitive</parameter>
        <parameter name="sex">Female</parameter>
        <parameter name="firstCapital">true</parameter>
        <parameter name="isOrdinal">false</parameter>
    </input>
    <output>
        <parameter name="outTable">table</parameter>
    </output>
</step>


В результате на выходе мы получим таблицу с именем table со следующими значениями: «Одной», «Тринадцати тысяч тринадцати», «Пятидесяти пяти миллионов ста тридцати двух тысяч ста одиннадцати», «Одного миллиарда пятидесяти пяти миллионов ста тридцати двух тысяч ста одиннадцати».

Обратите внимание, что параметры inTable и outTable имеют один тип ReportTable. Так как в данном случае выходной параметр имеет то же имя, что и входной, он просто заменит собой входной параметр в коллекции глобальных параметров. Таким образом можно легко делать цепочки преобразований.

Преобразования вещественных чисел


Для преобразований вещественных чисел используются действия ConvertFloatToString, ConvertFloatCellToString, ConvertFloatColToString. Преобразования вещественных чисел во многом схожи с преобразованиями целых за исключением того, что при преобразовании вещественных чисел нельзя задавать пол и какое это числительное (порядковое или количественное). Рассмотрим простой пример преобразования числа 3,1415926 в предложный падеж:

Пример
<step plugin="ConvertModule" action="ConvertFloatToString" repeat="1">
    <input>
        <parameter name="number">3,1415926</parameter>
        <parameter name="textCase">Prepositional</parameter>
        <parameter name="firstCapital">false</parameter>
    </input>
    <output>
        <parameter name="words"></parameter>
    </output>
</step>


В результате в параметр words будет записано значение «трех целых одном миллионе четырехстах пятнадцати тысячах девятистах двадцати шести десятимиллионных». Несмотря на то, что в качестве входного параметра действий преобразования вещественного числа используется System.Double, максимальный размер вещественной части ограничен миллиардными (9 знаков после запятой). Технически ничего не мешает сделать и больше, но на практике необходимости в такой точности пока не возникало.

Преобразования даты и времени


Для преобразования даты и времени служат действия ConvertDateTimeToString, ConvertDateTimeCellToString, ConvertDateTimeColToString. Как и предыдущие группы действий описываемые в этом разделе действия работы с датой и временем между собой очень схожи. Однако в отличие от преобразований целых и вещественных чисел, эти действия не используют параметры падежа и пола. Вместо этого используется более гибкий параметр format. Рассмотрим подробнее, какие представления форматов поддерживаются действиями преобразования даты:

Форматы
  • dd — день месяца в виде числа
  • ddx — день месяца в виде текста
  • MM — месяц в виде числа
  • MMx — месяц в виде текста
  • yy — год в формате двухзначного числа
  • yyx — год в формате двухзначного числа в виде текста
  • yyyy — год в формате четырехзначного числа
  • yyyyx — год в формате четырехзначного числа в виде текста
  • hh — время от 1 до 12 в виде числа
  • hhx — время от 1 до 12 в виде текста
  • HH — время от 0 до 23 в виде числа
  • HHx — время от 0 до 23 в виде текста
  • mm — минуты в виде числа
  • mmx — минуты в виде текста
  • ss — секунды в виде числа
  • ssx — секунды в виде текста


Во всех приведенных выше форматах буква «х» означает первую букву падежа n (Nominative), g (Genetive), d (Dative), a (Accusative), i (Instrumental), p (Prepositional).

Как видите, возможности по настройке формата представления даты огромные. Рассмотрим несколько конкретных примеров форматов и результатов. За исходное значение даты возьмем «1988-06-26 13:47:56» (или если вам больше нравится, можно записать ее в формате «26.06.1988 13:47:56», это не принципиально):

Формат: dd MMg yyyy года HHn mmn ssn
Результат: 26 июня 1988 года тринадцать часов сорок семь минут пятьдесят шесть секунд
Формат: ddn MMg yyyyg
Результат: двадцать шестое июня одна тысяча девятьсот восемьдесят восьмого года
Формат: dd.MM.yy (MMn yyyyg)
Результат: 26.06.88 (июнь одна тысяча девятьсот восемьдесят восьмого года)

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

Пример
<step plugin="ConvertModule" action="ConvertDateTimeToString" repeat="1">
    <input>
        <parameter name="dateTime">26.06.1988 13:47:56</parameter>
        <parameter name="format">dd.MM.yy (MMn yyyyg)</parameter>
        <parameter name="firstCapital">false</parameter>
    </input>
    <output>
        <parameter name="words"></parameter>
    </output>
</step>


Преобразования денежных сумм


Для преобразования представления денежных сумм в ActivityManager служат действия CurrencyToString, CurrencyCellToString и CurrencyColToString. Как и все предыдущие группы действий по преобразованию данных эти действия отличаются между собой только типами объектов, над которыми производится преобразование: скалярное значение, строка и таблица. Приведем простой пример преобразования. Предположим, что в базе данных суммы хранятся в поле типа decimal, поле имеет имя dept, и нам необходимо сформировать по ним отчет, в котором сумма будет представлена в формате числа с разделителями между тысячами, а в скобках должна быть расшифровка суммы письменно. Пропустим этап выборки данных из базы, как это делается было рассказано в разделе «Выборка данных». Непосредственно преобразование столбца по поставленной задаче будет выглядеть следующим образом:

Пример
<step plugin="ConvertModule" action="ConvertCurrencyColToString" repeat="1">
    <input>
        <parameter name="inTable">[table]</parameter>
        <parameter name="column">dept</parameter>
        <parameter name="currencyType">Ruble</parameter>
        <parameter name="format">nii,ff (nniin rn ffn kn)</parameter>
        <parameter name="thousandSeparator"> </parameter>
        <parameter name="firstCapital">false</parameter>
        <parameter name="isOrdinal">false</parameter>
    </input>
    <output>
        <parameter name="outTable">table</parameter>
    </output>
</step>


В результате преобразования мы получим таблицу, столбец dept которой преобразован из числового формата в текстовый. Причем все значения в данном столбце будут иметь вид «3 101 203,03 (три миллиона сто одна тысяча двести три рубля три копейки)».
Рассмотрим подробнее поддерживаемые форматы преобразования сумм:

Форматы
  • ii — рубли (доллары, евро) в виде числа
  • ff — копейки (центы) в виде числа
  • iix — рубли (доллары, евро) в виде строки
  • ffx — копейки (центы) в виде строки
  • rx — слово «рубль» («доллар», «евро») — какое именно слово будет выбрано зависит от параметра currencyType
  • kx — слово «копейка» («цент») — какое именно слово будет выбрано зависит от параметра currencyType
  • nn — слово «минус », если число отрицательное. Если число положительное, то пустая строка. Пробел после слова минус ставится автоматически.
  • n — знак "-", если число отрицательное. Если число положительное, то знак не ставится. Пробел после знака "-" автоматически НЕ ставится.

Во всех приведенных выше форматах буква «х» означает первую букву падежа n (Nominative), g (Genetive), d (Dative), a (Accusative), i (Instrumental), p (Prepositional).

Как и с форматами даты и времени в случае с денежными суммами возможно сформировать представление в самых невообразимых формах.

Преобразования ФИО


Нередкими являются ситуации, когда появляется необходимость склонения по падежам фамилии, имени и отчества в формируемом отчете. ActivityManager использует для склонений ФИО всем известную библиотеку Padeg.dll. Действия ConvertModule.dll для склонения по падежам ФИО называются ConvertNameToCase, ConvertNameCellToCase и ConvertNameColToCase. Пользоваться данными действиям не сложнее, чем всеми остальными действиями преобразования данных. Например, чтобы преобразовать ФИО «Сухов Зигмунд Эдуардович» можно воспользоваться следующей конфигурацией:

Пример
<step plugin="ConvertModule" action="ConvertNameToCase" repeat="1">
    <input>
        <parameter name="nameIn">Сухов Зигмунд Эдуардович</parameter>
        <parameter name="format">ss nn pp</parameter>
        <parameter name="textCase">Genitive</parameter>
    </input>
    <output>
        <parameter name="nameOut">words</parameter>
    </output>
</step>


Результатом выполнения данного действия будет строка «Сухова Зигмунда Эдуардовича».
Формат преобразования format очень прост и поддерживает следующие ключевые конструкции:

Форматы
  • ss — полное представление фамилии
  • s — первая буква фамилии
  • nn — полное представление имени
  • n — первая буква имени
  • pp — полное представление отчества
  • p — первая буква отчества

К примеру, если нам необходимо преобразовать «Сухова Зигмунда Эдуардовича» для подписи документа, мы можем воспользоваться шаблоном n.p. ss (при этом указав именительный падеж в параметре textCase) и получим в результате строку «З.Э. Сухов».

Действия объединения данных


Помимо действий по преобразованию формата представления данных в плагине ConvertModule.dll имеются простые действия, позволяющие объединить ячейки объектов ReportTable и ReportRow в строку с указанием разделителей. Эти действия называются RowConcat, ColumnConcat и TableConcat. Действие RowConcat служит для объединение всех ячеек объекта ReportRow в одну строку с указанием разделителя между значениями ячеек. Действие ColumnConcat позволяет объединить все ячейки одного столбца объекта ReportTable. Действие TableConcat позволяет объединить все ячейки объекта ReportTable с указанием разделителей между ячейками одной строки и строками. Пример использования

Пример
<step plugin="ConvertModule" action="TableConcat" repeat="1">
    <input>
        <parameter name="inTable">[myTable]</parameter>
        <parameter name="rowSeparator">;</parameter>
        <parameter name="cellSeparator">,</parameter>
    </input>
    <output>
        <parameter name="outValue">myConcatedStr</parameter>
    </output>
</step>


В результате исполнения действия по представленном примеру в коллекцию глобальных параметров будет записан параметр myConcatedStr строкового типа, значением которого будут все данные из таблицы myTable, объединенные объявленными разделителями (rowSeparator — разделитель строк, cellSeparator — разделитель ячеек).

Действия выборки элементов


К действиям выборки элементов относятся GetRow и GetCell.

Действие GetRow позволяет получить объект класса ReportRow из объекта класса ReportTable по указанному номеру строки (нумерация начинается с нуля). Объект класса ReportRow необходим для задания группового сопоставления шаблонных строк и значений (подробнее смотрите в разделе «Формирование отчета»). Важно помнить, что при отсутствии в объекте ReportTable строки с указанным номером будет сгенерировано исключение выхода индекса за диапазон возможных значений. Пример использования действия GetRow:

Пример
<step plugin="ConvertModule" action="GetRow" repeat="1">
    <input>
        <parameter name="table">[table]</parameter>
        <parameter name="rowNumber">0</parameter>
    </input>
    <output>
        <parameter name="row"></parameter>
    </output>
</step>


Действие GetCell предназначено для получения скалярного значения отдельной ячейки объекта ReportTable:

Пример
<step plugin="ConvertModule" action="GetCell" repeat="1">
   <input>
      <parameter name="table">[table]</parameter>
      <parameter name="rowNumber">0</parameter>
      <parameter name="columnName">myColumn</parameter>
   </input>
   <output>
      <parameter name="value"></parameter>
   </output>
</step>


Нумерация строк в действии GetCell также как и в GetRow начинается с нуля.

Формирование отчета


После выборки и преобразования данных следующим этапом является непосредственно вставка результатов в файл шаблона отчета.
ActivityManager поддерживает возможность формирования отчетов в форматы odt, ods, docx и xlsx. Работа со всеми этими форматами единообразна и с точки зрения конфигурации ничем не отличается. Отличия имеются только в пост-обработке, но об этом будет написано в соответствующем разделе. Одной из основных концепций был отказ от использования COM-технологий для формирования отчетов из-за их медлительности, зависимости от установленного текстового процессора и плохой переносимости на новые версии. В результате было принято решение формировать отчеты взаимодействуя с XML. Такой подход в жертву гибкости, которая присуща COM по форматированию данных, дает более высокую скорость работы и полную независимость от установленного текстового процессора. По сути текстовый процессор вообще может быть не установлен на компьютере, но отчет будет сформирован и открыт, к примеру, в WordPad или другом редакторе, ассоцированном с типом файла отчета.

Создание файла шаблона


Прежде чем приступить к настройке файла конфигурации для генерации отчета необходимо создать файл шаблона отчета. Файл шаблона отчета — это обычный файл формата odt, ods, docx или xlsx с заданной заранее конструкцией и форматированием и указанными шаблонными строками. Подставляемые значения в файле шаблона экранируются символами доллара. Например: $variable$, где variable — имя шаблонной строки. Чтобы было более понятно, посмотрите на изображение ниже.



В данном файле шаблона определено 6 шаблонных строк: $title$, $date$, $n$, $snp$, $money$, $date$. Как именно данные шаблонные строки будут заменяться в файле шаблона зависит от конфигурации.

Настройка конфигурации


Для работы непосредственно с самими файлами шаблонов отчетов в ActivityManager предусмотрен плагин ReportModule.dll. Этот плагин очень прост в использовании и определяет всего 5 действий.

Первым из рассматриваемых действий будет ReportSetTemplateFile. Это действие предназначено для установки пути до файла шаблона, на основании которого будет формироваться отчет. Этот файл уже должен существовать. Рассмотрим пример конфигурации для данного действия:

Пример
<step plugin="ReportModule" action="ReportSetTemplateFile" repeat="1">
    <input>
        <parameter name="fileName">[reportPath]example.odt</parameter>
    </input>
</step>


Здесь параметр [reportPath] определяет путь до папки, в которой хранится файл example.odt и передается в командной строке при вызове ActivityManager.exe или устанавливается на более ранних шагах. Безусловно нам ничего не мешает задать и абсолютный путь до файла шаблона и тогда никаких дополнительных параметров передавать необходимости не будет: Установка пути до файла шаблона является обязательным действием перед вызовом действия генерации отчета.

Помимо установки пути до файла шаблона до генерации отчета необходимо настроить сопоставления шаблонных строк и значений, на которые вы хотите их заменить. Для этого в плагине ReportModule.dll предназначены три действия ReportSetStringValue, ReportSetStringValues, ReportSetTableValue. Первые два действия предназначены для замены скалярных шаблонных строк. Рассмотрим их по порядку.

Действие ReportSetStringValue явно задает сопоставление шаблонной строки и значения, на которое она будет заменена. При запуске генерации отчета данное значение будет подставлено вместо заданной шаблонной строки по всему файлу шаблона. Необходимости в указании символов "$" в имени шаблонной строки при использовании данного действия нет. Приведем простой пример использования данного действия:

Пример
<step plugin="ReportModule" action="ReportSetStringValue" repeat="1">
    <input>
        <parameter name="name">title</parameter>
        <parameter name="value">Заголовок 123</parameter>
    </input>
</step>


В результате при генерации отчета по файлу шаблона с предыдущего изображения получится следующий результат



Может быть утомительным устанавливать сопоставление каждой шаблонной строки и соответствующего значения из источника данных отдельно. Да еще и перед тем как его установить, необходимо выбрать значение из результирующего набора ReportTable при помощи действия GetCell. Хотя теоретически это возможно, делать так не рекомендуется. Это действие по большей части предназначено для установки сложных вычисляемых значений или значений, переданных в командной строке. При необходимости установки скалярных значений из источника данных (например, РСУБД), служит более удобное действие ReportSetStringValues. Этому действию передается всего один параметр типа ReportRow. Действие делает сопоставление названий столбцов(ячеек) именам шаблонных строк. Для пояснения возьмем следующий пример:

Пример
<step plugin="SqlDataSource" action="SqlSetConnectionString" repeat="1">
    <input>
        <parameter name="connectionString">dsn=registry</parameter>
    </input>
</step>
<step plugin="SqlDataSource" action="SqlSelectTable" repeat="1">
    <input>
        <parameter name="query">SELECT 'Заголовок 321' AS title, NOW() AS date</parameter>
    </input>
    <output>
        <parameter name="table"></parameter>
    </output>
</step>
<step plugin="ConvertModule" action="GetRow" repeat="1">
    <input>
        <parameter name="table">[table]</parameter>
        <parameter name="rowNumber">0</parameter>
    </input>
    <output>
        <parameter name="row"></parameter>
    </output>
</step>
<step plugin="ReportModule" action="ReportSetStringValues" repeat="1">
    <input>
        <parameter name="values">[row]</parameter>
    </input>
</step>
<step plugin="ReportModule" action="ReportSetTemplateFile" repeat="1">
    <input>
        <parameter name="fileName">C:\example.odt</parameter>
    </input>
</step>
<step plugin="ReportModule" action="ReportGenerate" repeat="1">
    <output>
        <parameter name="fileName"></parameter>
    </output>
</step>
<step plugin="IOModule" action="IOOpenFile" repeat="1">
    <input>
        <parameter name="fileName">[fileName]</parameter>
        <parameter name="arguments"></parameter>
    </input>
</step>


На первых двух шагах примера выше мы устанавливаем строку соединения и делаем выборку объекта ReportTable в коллекцию глобальных параметров. Этот объект имеет имя table и представляет собой таблицу с одной строкой и столбцом с именами title и date. После этого мы при помощи действия GetRow выбираем из объекта ReportTable объект ReportRow, представляющий собой строку по индексу 0 (первая строка) объекта ReportTable. После чего устанавливаем объект ReportRow в качестве перечня сопоставлений значений и шаблонных строк в действии ReportSetStringValues. В результате всех этих действий будет сформирована конфигурация, которая «говорит», что необходимо искать в файле шаблона строки $title$ и $date$ и менять их на значения из соответствующих столбцов(ячеек) строки ReportRow. После формирования отчета по рассмотренной выше конфигурации для ранее созданного файла шаблона example.odt мы получим результат, представленный на рисунке ниже



Обратите внимание, что во время вставки скалярных значений в четвертом столбце таблицы произошла замена одноименной шаблонной строки $date$. Дело в том, что действия сопоставления шаблонных строк значениям ReportSetStringValue и ReportSetStringValues не делают никаких предположений о том, где расположены данные: в таблице, в колонтитуле, в плавающем текстовом блоке или где-то еще, они просто производят замену по шаблону. Одним из решений данной проблемы может служить переименование одной из шаблонных строк. Другой способ будет рассказан ниже в разделе «Порядок установки сопоставлений шаблонных строк».

При групповой установке сопоставлений шаблонных строк действием ReportSetStringValues вовсе не обязательно чтобы всем столбцам(ячейкам) из строки ReportRow соответствовали шаблонные строки в файле. Шаблонные строки, которые не удалось найти в файле при формировании просто будут отброшены. Справедливо и обратное, если в файле шаблона имеются шаблонные строки, которым не сопоставлено ни одного значения, то они просто не будут заменены. Никаких ошибок при этом не будет.

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

Для вставки табличных данных служит действие ReportSetTableValue. Сигнатура данного действия выглядит следующим образом:

Пример
<step plugin="ReportModule" action="ReportSetTableValue" repeat="1">
    <input>
        <parameter name="table">[table]</parameter>
        <parameter name="xmlContractor">Row</parameter>
    </input>
</step>


Параметр table представляет собой объект типа ReportTable, данные из которого планируется вставить в файл шаблона. Поиск соответствия в файле шаблона производится по названиям столбцов объекта ReportTable. Порядок столбцов значения не имеет, но имеет значение их наличие. Важным моментом в замене табличных данных является то, что поиск соответствия шаблонных строк идет по похожести. Генератор отчета делает предположение, что если он нашел блок XML-разметки документа, удовлетворяющий xmlContractor (про xmlContractor смотрите ниже) и при этом 50 или более процентов столбцов ReportTable соответствует шаблонным строкам в файле шаблона, то это искомый элемент и необходимо произвести подстановку. К примеру, предположим, что у нас имеется в файле шаблона таблица, изображенная ранее (на первом рисунке). В таблице имеются шаблонные строки $n$, $snp$, $money$, $date$. Мы передаем в действие ReportSetTableValue объект ReportTable, в котором имеются столбцы n, snp, money, date, time, action, pay. В результате будет произведена замена, т.к. удалось сопоставить четыре из семи столбцов ReportTable шаблонным строкам (более 50% совпадений), расположенным в строке таблицы.

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



Параметр xmlContractor имеет особое значение для действия ReportSetTableValue. Этот параметр определяет элементы какого типа необходимо искать в файле шаблона на соответствие объекту ReportTable. И при подстановке именно объекты этого типа будут дублироваться. Всего существует 4 вида xmlContractor: Paragraph, Table, Row, Cell. Их отличие проще продемонстрировать на примере. Рассмотрим простой файл шаблона, в котором мы хотим произвести замену значений



В нем имеется таблица и параграф. Шаблонные строки в таблице и параграфе оформлены схожим образом. При установке xmlContractor в значение Row будет производиться поиск строк в каждой из существующих таблиц файла шаблона на соответствие столбцам объекта ReportTable. При нахождении удовлетворяющего схожести элемента строки он будет взят за шаблон и продублирован столько раз, сколько строк имеется в объекте ReportTable с заменой шаблонных строк на значения из соответствующих строк ReportTable. Результат будет выглядеть как на изображении ниже



Как видим, элемент параграфа был вовсе проигнорирован, т.к. он не вложен ни в один из элементов строки таблицы в документе.
Если xmlContractor установить в значение Table, то мы получим результат, изображенный ниже



На первый взгляд может быть непонятно что именно произошло, но принцип тот же, что и со строками. Только в этот раз производится поиск элементов таблиц на соответствие объекту ReportTable и при нахождении такой таблицы производится ее копирование для каждой строки объекта ReportTable с заменой шаблонных строк. На рисунке выше мы видим три таблицы. По одной для каждой строки объекта ReportTable. Применение xmlContractor Table позволяет формировать более сложные отчеты, где данные расположены не построчно: блоки подписи, вертикальные карточки данных и многое другое. Стоит заметить, что продемонстрированное поведение xmlContractor Table характерно только для отчетов в odt и docx. При формировании отчета в ods с установленным xmlContractor Table будет произведено копирование листа (при нахождении соответствия) для каждой строки в объекте ReportTable. Также важно заметить, что на данный момент это единственный xmlContractor, который не поддерживается при формировании отчетов в xlsx.
На изображении ниже представлен результат установки xmlContractor Paragraph



Как и ожидалось в данном случае было произведено копирование параграфа для каждой строки в ReportTable. При помощи этого xmlContractor удобно создавать списки.
xmlContractor Cell имеет особое значение для файлов табличных процессоров xlsx и ods. Для файлов текстовых процессоров он не имеет смысла. Для примера возьмем следующий файл шаблона в формате ods:



После чего применим действие ReportSetTableValue, устанавливающее таблицу с одним столбцом value.
При использовании xmlContractor Row мы получим вполне ожидаемый результат:



Строки таблицы ods, в которых было найдено соответствие объекту ReportTable (в данном примере такая строка одна, но это не обязательно), были продублированы для каждой строки объекта ReportTable.

А вот так будет выглядеть результат, если установить xmlContractor Cell:



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

И, наконец, последнее не рассмотренное действие в плагине ReportModule.dll — это ReportGenerate. Пример использования данного действия был приведен ранее

Пример
<step plugin="ReportModule" action="ReportGenerate" repeat="1">
    <output>
        <parameter name="fileName"></parameter>
    </output>
</step>


Это действие предназначено для формирования отчета. Дело в том, что все до этого рассмотренные действия плагина ReportModule.dll конфигурируют генератор отчета, но не производят непосредственно генерацию. Генерация отчета происходит при вызове действия ReportGenerate. Именно поэтому это действие необходимо вызывать после всех настроек. Выходящим параметром действия ReportGenerate является путь до сформированного файла отчета. ReportGenerate не производит открытие файла отчета после формирования, это следует помнить. Причиной такого поведения является модульность. Генератор отчета не может знать, что вы планируете делать со сформированным отчетом. Возможно вы захотите написать плагин для отправки отчета по электронной почте после формирования или для записи его на FTP-сервер или еще что-то. Для выполнения самого ожидаемого действия со сформированным отчетом (его открытия) в ActivityManager предусмотрено действие. Оно называется IOOpenFile и как можно догадаться из названия расположено в плагине IOModule. Пример его использования приведен ниже:

Пример
<step plugin="IOModule" action="IOOpenFile" repeat="1">
    <input>
        <parameter name="fileName">[fileName]</parameter>
        <parameter name="arguments"></parameter>
    </input>
</step>


При выполнение этого действия файл будет открыт в программе, ассоциированной с расширением файла. Т.е., если мы формируем отчет, а на компьютере не установлено ни OpenOffice, ни LibreOffice, ни MS Word, ничего кроме WordPad, то файл скорее всего откроется именно в нем.

Порядок установки сопоставлений шаблонных строк


При установке сопоставлений шаблонных строк значениям действиями ReportSetStringValue, ReportSetStringValues, ReportSetTableValue важно понимать, что замена во время генерации отчета производится каскадно в порядке их объявления. Это значит, что значения параметров на раннем шаге могут служить шаблонными строками параметров на более позднем шаге. Рассмотрим на примере.

Пример
<step plugin="ReportModule" action="ReportSetStringValue" repeat="1">
    <input>
        <parameter name="name">content</parameter>
        <parameter name="value">My name is $name$. My surname is $surname$.</parameter>
    </input>
</step>
<step plugin="ReportModule" action="ReportSetStringValue" repeat="1">
    <input>
        <parameter name="name">name</parameter>
        <parameter name="value">Vasily</parameter>
    </input>
</step>
<step plugin="ReportModule" action="ReportSetStringValue" repeat="1">
    <input>
        <parameter name="name">surname</parameter>
        <parameter name="value">Ignatov</parameter>
    </input>
</step>


Как видно из этого примера сначала будет произведена замена шаблонной строки $content$ на значение «My name is $name$. My surname is $surname$.». После чего шаблонные строки $name$ и $surname$ будут заменены соответственно на «Vasily» и «Ignatov». Результирующей строкой в файле шаблона при этом будет «My name is Vasily. My surname is Ignatov.». Однако, если поменять местами шаги и поставить шаг, устанавливающий значение шаблонной строки $content$ в самый низ, то замены шаблонных строк $name$ и $surname$ не произойдет (этих строк еще просто нет на данном этапе формирования отчета). В результате в сформированном отчете будет получена строка «My name is $name$. My surname is $surname$.». Каскадная замена позволяет формировать отчеты с сильной зависимостью представления данных от значений входных параметров.

Пост-обработка файла шаблона


Под пост-обработкой понимается процесс форматирования представления текста уже после заполнения файла шаблона данными. Это необходимо, если перед нами стоит задача, к примеру, выделить жирным часть текста уже после вставки. В качестве конкретного примера рассмотрим договор мены жилья. По договору количество участников может быть неограниченным, но в перечне участников с каждой из сторон необходимо вывести их всех одним абзацем (не списком), причем известно, что в информацию по каждому участнику включается его ФИО и паспортные данные, НО только ФИО необходимо выделить жирным. Если бы для решения этой задачи можно было использовать списки, то мы бы воспользовались ReportSetTableValue с установленным xmlContractor = Paragraph и заранее настроили бы стили в файле шаблона для каждого параметра. Но так как вывести всех участников необходимо одним абзацем мы можем воспользоваться тэгами пост-обработки. При выборке данных мы объединяем всех участников и их паспортные данные в одну строку при помощи GROUP_CONCAT в MySQL, или FOR XML в MSSQL, или TableConcat плагина ConvertModule.dll, или каким-то другим более привычным для вас образом.

Причем при объединении заключаем ФИО в специальный тэг $b$value$/b$. Т.е. в результате получится строка примерно такого содержания: "$b$Игнатов Василий Васильевич$/b$, 29.06.1988 г.р., паспорт № 0123456 серия 4321 ..., $b$Иванов Иван Иванович$/b$, 01.15.1976 г.р., паспорт № 654321 серия 1234, ...". После вставки этой строки как обычного значения в файл шаблона и после завершения замены всех остальных шаблонных строк будет запущен пост-обработчик спец. тэгов. Он заменит все тэги $b$ $/b$ на жирный стиль, т.е. весь текст внутри этих тэгов станет жирным:

На данный момент пост-обработка поддерживается только в отчетах OpenOffice.
Существуют следующие специальные тэги пост-обработки:

  • $b$ $/b$ — весь текст в этом тэге будет жирным
  • $i$ $/i$ — весь текст в этом тэге будет курсивом
  • $u$ $/u$ — весь текст в этом тэге будет подчеркнутым
  • $br$ — тэг будет заменен на перенос строки с созданием нового абзаца (аналог Enter в OpenOffice)
  • $sbr$ — тэг будет заменен на перенос строки без создания нового абзаца (аналог Shift+Enter в OpenOffice)

Старайтесь не задавать шаблонные строки с именами из этого перечня.

Действия условного перенаправления и JS макросы


Условия


ActivityManager поддерживает два действия для условного перенаправления порядка исполнения. Оба этих действия расположены в плагине IOModule.dll. К ним относятся IOIfConditionToStep и IOIfConditionExit. Как можно понять из названия действий IOIfConditionToStep позволяет перейти на указанный шаг, если условие true, а IOIfConditionExit завершает обработку шагов конфигурационного файла, если условие true. Рассмотрим конкретный пример. Предположим, что нам необходимо сформировать соглашения по социальному, коммерческому и специализированному найму жилья. Все три отчета имеют схожу структуру и одинаковые (ну или почти одинаковые, это не принципиально) шаблонные строки, и отличаются лишь немного текстом соглашения. По этой причине нам нет необходимости создавать несколько файлов конфигурации. Достаточно создать несколько файлов шаблона, а в файле конфигурации добавить условие: если тип найма — социальный, то использовать файл шаблона социального найма, если тип найма — коммерческий, то использовать файл шаблона коммерческого найма и т.д.

Пример
<!-- step 5 -->
<step plugin="JSModule" action="JSRun" repeat="1">
    <input>
        <parameter name="script">return [id_rent_type] != 1</parameter>
    </input>
    <output>
        <parameter name="result">condition</parameter>
    </output>
</step>
<!-- step 6 -->
<step plugin="IOModule" action="IOIfCondititonToStep" repeat="1">
    <input>
        <parameter name="condition">[condition]</parameter>
        <parameter name="step">8</parameter>
    </input>
</step>
<!-- step 7 -->
<step plugin="ReportModule" action="ReportSetTemplateFile" repeat="1">
    <input>
        <parameter name="fileName">\\nas\media$\ActivityManager\templates\registry\tenancy\agreement_commercial.odt</parameter>
    </input>
</step>
<!-- step 8 -->
<step plugin="JSModule" action="JSRun" repeat="1">
    <input>
        <parameter name="script">return [id_rent_type] != 2</parameter>
    </input>
    <output>
        <parameter name="result">condition</parameter>
    </output>
</step>
<!-- step 9 -->
<step plugin="IOModule" action="IOIfCondititonToStep" repeat="1">
    <input>
        <parameter name="condition">[condition]</parameter>
        <parameter name="step">11</parameter>
    </input>
</step>
<!-- step 10 -->
<step plugin="ReportModule" action="ReportSetTemplateFile" repeat="1">
    <input>
        <parameter name="fileName">\\nas\media$\ActivityManager\templates\registry\tenancy\agreement_special.odt</parameter>
    </input>
</step>
<!-- step 11 -->
<step plugin="JSModule" action="JSRun" repeat="1">
    <input>
        <parameter name="script">return [id_rent_type] != 3</parameter>
    </input>
    <output>
        <parameter name="result">condition</parameter>
    </output>
</step>
<!-- step 12 -->
<step plugin="IOModule" action="IOIfCondititonToStep" repeat="1">
    <input>
        <parameter name="condition">[condition]</parameter>
        <parameter name="step">14</parameter>
    </input>
</step>
<!-- step 13 -->
<step plugin="ReportModule" action="ReportSetTemplateFile" repeat="1">
    <input>
        <parameter name="fileName">\\nas\media$\ActivityManager\templates\registry\tenancy\agreement_social.odt</parameter>
    </input>
</step>
<!-- step 14 -->
<step plugin="JSModule" action="JSRun" repeat="1">
    <input>
        <parameter name="script">return ([id_rent_type] == 1) || ([id_rent_type] == 2) || ([id_rent_type] == 3)</parameter>
    </input>
    <output>
        <parameter name="result">condition</parameter>
    </output>
</step>
<!-- step 15 -->
<step plugin="IOModule" action="IOIfCondititonToStep" repeat="1">
    <input>
        <parameter name="condition">[condition]</parameter>
        <parameter name="step">18</parameter>
    </input>
</step>
<!-- step 16 -->
<step plugin="IOModule" action="IODebugMessage" repeat="1">
    <input>
        <parameter name="message">Вы пытаетесь распечатать соглашение на неподдерживаемый тип найма</parameter>
    </input>
</step>
<!-- step 17 -->
<step plugin="IOModule" action="IOIfCondititonExit" repeat="1">
    <input>
        <parameter name="condition">true</parameter>
    </input>
</step>


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



В приведенной выше конфигурации помимо уже упоминавшихся действий IOIfConditionToStep и IOIfConditionExit есть еще одно действие, требующее особого внимания. Это действие JSRun плагина JSModule.dll. Это действие позволяет реализовывать микровычисления (инкременты, проверку значений и прочее) в виде JavaScript-макросов. В первом параметре действия JSRun передается обычный скрипт на языке JavaScript. Так как этот скрипт является обычным текстом с точки зрения ядра ActivityManger, в нем можно делать любые подстановки через [параметры]. JSRun является надстройкой над известной библиотекой Noesis.Javascript и поддерживает все синтаксические конструкции, поддерживаемые данной библиотекой. Для получения результата из скрипта его необходимо вернуть инструкцией return или присвоив значение специальной переменной окружения скрипта с именем result.

Циклы


Помимо условий при помощи действия IOIfConditionToStep совместно с JSRun можно реализовывать и циклы. Предположим мы имеем в базе данных информацию по пользователям, их платежам и датам внесения платежа в следующем виде



и нам необходимо сформировать отчет вида



При этом заранее не известно количество лет, а соответственно и таблиц. В данном отчете платежи сгруппированы по годам и разнесены по разным таблицам. Приступим.

Чтобы сформировать этот отчет нам понадобится вот такой простой файл шаблона



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

После этого необходимо выбрать список годов и сопоставлений шаблонов. Так эти данные будут выглядит в объекте ReportTable (назовем эту таблицу yearsTable):



Представленные выше данные необходимо вставить действием ReportSetTableValue с xmlClouser = Table в шаблонный файл. В результате получится заготовка, изображенная ниже (вы ее не увидите во время формирования отчета)



И наконец, после этого необходимо пройтись по всем строкам таблицы yearsTable, выбрать из них действием GetCell значение года (назовем его currentYear) и для каждой строки таблицы yearsTable выполнить запрос к базе данных, представленный ниже (пример для MySQL)

Запрос
SELECT @n := @n + 1 AS n[currentYear], snp AS snp[currentYear],
money AS money[currentYear], DATE_FORMAT(date,'%d.%m.%Y') AS date[currentYear]
FROM test, (SELECT @n := 0) v
WHERE YEAR(date) = '[currentYear]'


После чего необходимо результат этого запроса (на каждой итерации) установить действием ReportSetTableValue с xmlClouser = Row в шаблонный файл.
Полный листинг конфигурационного файла для данной задачи представлен ниже:

Листинг
<?xml version="1.0" encoding="utf-8"?>
<activity>
    <plugins>
        <include>ConvertModule.dll</include>
        <include>IOModule.dll</include>
        <include>JSModule.dll</include>
        <include>ReportModule.dll</include>
        <include>SqlDataSource.dll</include>
    </plugins>
    <language>ru</language>
    <step plugin="SqlDataSource" action="SqlSetConnectionString" repeat="1">
        <input>
            <parameter name="connectionString">dsn=registry</parameter>
        </input>
    </step>
    <step plugin="SqlDataSource" action="SqlOpenConnection" repeat="1" />
    <step plugin="SqlDataSource" action="SqlSelectTable" repeat="1">
        <input>
            <parameter name="query">
                SELECT DISTINCT YEAR(date) AS year,
                    CONCAT('$n',YEAR(date),'$') AS n,
                    CONCAT('$snp',YEAR(date),'$') AS snp,
                    CONCAT('$money',YEAR(date),'$') AS money,
                    CONCAT('$date',YEAR(date),'$') AS date
                FROM test
                ORDER BY year;
            </parameter>
        </input>
        <output>
            <parameter name="table">yearsTable</parameter>
        </output>
    </step>
    <step plugin="ReportModule" action="ReportSetTemplateFile" repeat="1">
        <input>
            <parameter name="fileName">C:\Users\IgnVV\Desktop\1.odt</parameter>
        </input>
    </step>
    <step plugin="ReportModule" action="ReportSetTableValue" repeat="1">
        <input>
            <parameter name="table">[yearsTable]</parameter>
            <parameter name="xmlContractor">Table</parameter>
        </input>
    </step>
    <step plugin="SqlDataSource" action="SqlSelectTable" repeat="1">
        <input>
            <parameter name="query">
                SELECT COUNT(*) AS count
                FROM (
                    SELECT DISTINCT YEAR(date) AS year
                    FROM test
                    ORDER BY year) v;
            </parameter>
        </input>
        <output>
            <parameter name="table">countYearsTable</parameter>
        </output>
    </step>
    <step plugin="ConvertModule" action="GetCell" repeat="1">
        <input>
            <parameter name="table">[countYearsTable]</parameter>
            <parameter name="rowNumber">0</parameter>
            <parameter name="columnName">count</parameter>
        </input>
        <output>
            <parameter name="value">countYears</parameter>
        </output>
    </step>
    <step plugin="JSModule" action="JSRun" repeat="1">
        <input>
            <parameter name="script">return [countYears] == 0</parameter>
        </input>
        <output>
            <parameter name="result">condition</parameter>
        </output>
    </step>
    <step plugin="IOModule" action="IOIfCondititonToStep" repeat="1">
        <input>
            <parameter name="condition">[condition]</parameter>
            <parameter name="step">15</parameter>
        </input>
    </step>
    <step plugin="JSModule" action="JSRun" repeat="1">
        <input>
            <parameter name="script">return [countYears]-1;</parameter>
        </input>
        <output>
            <parameter name="result">countYears</parameter>
        </output>
    </step>
    <step plugin="ConvertModule" action="GetCell" repeat="1">
        <input>
            <parameter name="table">[yearsTable]</parameter>
            <parameter name="rowNumber">[countYears]</parameter>
            <parameter name="columnName">year</parameter>
        </input>
        <output>
            <parameter name="value">currentYear</parameter>
        </output>
    </step>
    <step plugin="SqlDataSource" action="SqlSelectTable" repeat="1">
        <input>
            <parameter name="query">
                SELECT @n := @n + 1 AS n[currentYear], 
                    snp AS snp[currentYear], 
                    money AS money[currentYear], 
                    DATE_FORMAT(date,'%d.%m.%Y') AS date[currentYear]
                FROM test, (SELECT @n := 0) v
                WHERE YEAR(date) = '[currentYear]';</parameter>
        </input>
        <output>
            <parameter name="table"></parameter>
        </output>
    </step>
    <step plugin="ReportModule" action="ReportSetTableValue" repeat="1">
        <input>
            <parameter name="table">[table]</parameter>
            <parameter name="xmlContractor">Row</parameter>
        </input>
    </step>
    <step plugin="IOModule" action="IOIfCondititonToStep" repeat="1">
        <input>
            <parameter name="condition">true</parameter>
            <parameter name="step">8</parameter>
        </input>
    </step>
    <step plugin="SqlDataSource" action="SqlCloseConnection" repeat="1" />
    <step plugin="ReportModule" action="ReportGenerate" repeat="1">
        <output>
            <parameter name="fileName"></parameter>
        </output>
    </step>
    <step plugin="IOModule" action="IOOpenFile" repeat="1">
        <input>
            <parameter name="fileName">[fileName]</parameter>
            <parameter name="arguments"></parameter>
        </input>
    </step>
</activity>


Визуальный редактор файлов конфигураций


Как можно было убедиться из предыдущих разделов, а особенно из раздела «Действия условного перенаправления и JS макросы», писать xml-конфигурацию вручную не только утомительно, но и чревато ошибками. Помимо этого в голове необходимо держать массу информации: какие имена у параметров, сколько их, какой они имеют тип, какие есть действия и в каких плагинах они расположены, какой порядковый номер у текущего шага и многое другое. Для облегчения создания файлов конфигурации отчетов был разработан визуальный редактор. На изображении ниже можно увидеть как выглядит этот редактор с открытым файлом конфигурации, демонстрирующим работу с циклами из предыдущего раздела



В левой части окна редактора расположен список шагов и их порядковых номеров (что очень удобно при работе с условным перенаправлением). Именно в таком порядке шаги будут располагаться в xml-файле. Снизу расположено 4 кнопки, назначение которых интуитивно понятно (слева направо): добавить шаг в текущую позицию, удалить текущий выделенный шаг, переместить выделенный шаг на одну позицию вверх, переместить выделенный шаг на одну позицию вниз. Для перемещения шагов не обязательно пользоваться кнопками, поддерживается drag'n'drop.

В правой части окна редактора расположены два выпадающих списка: «Плагины» (с перечнем подключенных на данный момент плагинов) и «Действия» (со списком действий доступных из данного плагина). Также в правой части расположена таблица с перечнем входных и выходных параметров. У каждого параметра отображается имя, тип, направление (входной или выходной) и значение. Первые три столбца задавать нет необходимости, они автоматически рефлексией формируются из сборок плагинов. В данной таблице необходимо указывать только значения. Чуть ниже, под таблицей с параметрами, расположено подробное описание выбранного действия. Эти описания берутся из xml-файлов документации, расположенных вместе с плагинами в папке plugins/. Подробнее о том, как формируются эти файлы можно почитать, например, тут.

Начало работы


При создании нового файла конфигурации первым делом необходимо подключить плагины, которые планируется использоваться. В противном случае они будут недоступны в выпадающем списке. Делается это просто: Главное меню -> Настройки -> Плагины. В результате на экране появится окно, в котором необходимо выбрать какие именно плагины мы хотим использовать.



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



В выпадающем списке этого редактора можно выбрать одно из видимых на данном этапе возможных значений либо можно задать произвольное значение вручную. В списке будут представлены все возможные значения перечисления (подгружаются рефлексией из сборки плагина), все параметры командной строки и те выходные параметры предыдущих шагов исполнения, которые совпадают по типу с задаваемым параметром.
Для параметров типа данных Sytem.String и сложных типов данных вызывается редактор на базе ScintillaNET с поддержкой подсветки синтаксиса JavaScript и SQL.



В этом редакторе также как и в упрощенном есть выпадающий список с видимыми на данном шаге параметрами из глобальной коллекции. Кнопка «Вставить» вставляет выбранный в списке параметр в место расположения курсора в редакторе.

Возможности отладки


Визуальный редактор поддерживает несколько простых возможностей, позволяющих отлаживать работоспособность файлов конфигураций без необходимости запуска ядра ActivityManager.exe из консоли или своего приложения.

Первым делом перед запуском файла конфигурации на исполнение необходимо установить параметры командной строки, которые вы планируете передавать ActivityManager.exe при запуске. Делается это в Главном меню -> Настройка -> Параметры командной строки. На экране появится окно, изображенное ниже



Обратите внимание, что обязательный параметр config, который передается ActivityManager.exe в этом окне задавать не надо. Все параметры, заданные в этом окне, будут доступны во время конфигурации шагов из выпадающего списка видимых возможных значений.
После настройки параметров командной строки можно произвести запуск файла конфигурации на исполнение. Для этого необходимо нажать клавишу F5 либо перейти в Главное меню -> Конфигурация -> Выполнить. В случае, если во время выполнения файла конфигурации произошла ошибка, вы получите сообщение с информацией на каком шаге произошла ошибка и текст самой ошибки.



Бывают случаи, когда информации по возникшей ошибке недостаточно, чтобы ее локализовать, либо ошибок вообще нет, но на выходе получается отчет, который не соответствует ожиданиям. В таких случаях может помочь вывод значений параметров через действие IODebugMessage плагина IOModule. Это действие принимает всего один параметр — сообщение, которое необходимо вывести. Приведение выводимых объектов к строковому типу перед выводом осуществляется методом ToString() поэтому для типов данных, у которых этот метод не переопределен отладочное окно выведет лишь имя класса объекта.
После настройки файла конфигурации и проверки, что все работает корректно, вы можете скопировать строку выполнения, которой необходимо вызывать ядро ActivityManager для формирования отчета из своего приложения или командной строки. Чтобы это сделать перейдите в Главное меню -> Конфигурация -> Копировать строку выполнения.

Настоятельно рекомендуется по возможности при создании файлов конфигурации отчетов пользоваться визуальным редактором, а не править xml-файлы вручную.

Написание собственных плагинов


Плагины представляют собой обычные .Net сборки, расположенные в каталоге plugins\ и реализованные по нескольким простым правилам:

  • В сборке должен быть объявлен публичный интерфейс IPlug
  • Интерфейс IPlug должен находиться в пространстве имен, название которого совпадает с названием сборки. Т.е., если мы пишем плагин, сборка которого имеет имя MenaModule.dll, то и название пространства имен, в котором расположен интерфейс IPlug должно быть MenaModule.
  • В сборке должен быть только один класс, реализующий интерфейс IPlug. В противном случае какой именно класс окажется видимым не определено (первый найденный при рефлексии).
  • В интерфейсе IPlug необходимо объявлять только действия. Возвращаемый параметр этих действий должен быть void. Выходные значения возвращаются через out-параметры.

Рассмотрим пример создания собственного плагина MyModule.dll:

Пример
namespace MyModule {
    public interface IPlug { 
        void MyAction(string input, out string output); 
    }

    public class MenaPlug: IPlug 
    { 
        public void MyAction(string input, out string output) 
        { output = input+input; 
        }
    }
}


Вот и все. После компиляции остается положить этот плагин в каталог plugins\ и ActivityManager с AMEditor автоматически его найдут. Использовать действие MyAction данного плагина можно будет через следующую конфигурацию:

Пример
    <step plugin="MyModule" action="MyAction" repeat="1">
        <input>
            <parameter name="input">test</parameter>
        </input>
        <output>
            <parameter name="output"></parameter>
        </output>
    </step>


Чуть более сложный пример custom-плагина можно посмотреть в файле MenaModule.cs

Примечание: чтобы работать в своих плагинах с объектами классов ReportTable и ReportRow необходимо добавить в проект ссылку на сборку ExtendedTypes.dll.

Заключение


Проект развивается уже около года и и используется на боевых задачах нашей организации. Как и у любого генератора отчетов у ActivityManager есть как достоинства так недостатки. О достоинствах было рассказано во введении, теперь же хотелось отметить недостатки:

  • Не поддерживается вставка изображений
  • Не поддерживается возможность объединения ячеек после вставки значений. Объединять ячейки необходимо во время заготовки шаблона. По этой причине некоторые особо сложные отчеты сформировать становится проблематично
  • Отсутствуют unit-тесты (и не только unit). Несмотря на то, что за год использования на боевых проектах большая часть функционала ActivityManager была неоднократно протестирована, это все-таки очень серьезный, но поправимый недостаток.

Несмотря на указанные недостатки, я надеюсь, что существуют люди, которым проект может показаться интересным. Возможно кто-то захочет использовать у себя в задачах этот менеджер отчетов, а может кто-то даже захочет поучаствовать в его разработке. В любом случае, если этим постом я заинтересую хоть одного человека, значит пост написан не зря. =)

Ссылки


Tags:
Hubs:
+7
Comments6

Articles

Change theme settings