Програмування звітів в FossLook
У даній статті ми вивчимо можливості FossLook з програмування звітів. Звіт - це по суті візуалізація пошуку (відбору) документів, призначена для друку. Для прикладу ми створимо свій тип документа з однією кнопкою "Побудувати звіт". При натисканні на кнопку будуть виконані відбір документів та подання їх у вигляді таблиці.
Так як нам буде потрібно програмувати картку документа, рекомендовано прочитати статтю "Програмування в FossLook" для отримання базових знань, необхідних для розуміння викладеного матеріалу .
Матеріали для завантаження
Завантажте підготовлені нами матеріали для створення звітів:
Реалізація звіту
Після імпорту бібліотеки-прикладу ви можете створити в ній один документ, який власне і буде звітом. У вкладених файлах даного документа необхідно створити файл Report.htm, вміст якого буде розглянуто нижче.
З точки зору реалізації для побудови звіту достатньо реалізувати відбір документів за умовами і виведення їх в деякому вигляді (в csv, MS Word-документ, таблиця тощо). У нашому прикладі ми використовуємо готові модулі (майстер з сторінкою вибору діапазону дат), тому нам буде потрібно підтримати інтерфейс Foss.FUIS.Wizard.Blocks.IAction для класу Form (форма картки документа).
Припустимо, наша задача - отримати всі папки всіх бібліотек і визначити кількість документів в них за деякий проміжок часу. Надалі подібним чином ви зможете модифікувати вихідний код звіту так, щоб виконувати відбір своїх документів з будь-яких папок, накладаючи різні умови.
Відкрийте файл Form.cs скрипта картки документа, щоб вивчити реалізацію звіту на C# (цей же файл ви знайдете в прикладі проекту Visual Studio):
public class Form : Foss.FossDoc.ObjectModel.DataRepresentation.View.DocumentCard.DocumentCardInplaceForm, Foss.FUIS.Wizard.Blocks.IAction
{
Foss.FUIS.Action _ActionBuildReport;
//Файл із шаблоном звіту
const string _FileTemplateName = "Report.htm";
protected override void InitializeActionsButtons()
{
base.InitializeActionsButtons();
_ActionBuildReport = new FUIS.Action();
_ActionBuildReport.Name = "_MyActionBuildReport";//це просто "технічне" ім'я команди
_ActionBuildReport.Text = "Побудувати звіт";//це ім'я буде бачити користувач (напис)
_ActionBuildReport.Perform += _ActionBuildReport_Perform;
_ActionManager.AppendAction(_ActionBuildReport);
}
EDMS.Plugins.ReportWizard.Pages.SelectRangeDatePage _DateRangePage;
void _ActionBuildReport_Perform(object sender, EventArgs e)
{
_DateRangePage= new EDMS.Plugins.ReportWizard.Pages.SelectRangeDatePage();
using (Foss.FossDoc.EDMS.Plugins.ReportWizard.Wizard wizard = new EDMS.Plugins.ReportWizard.Wizard(this, new FUIS.Wizard.Page[] { _DateRangePage }))
{
wizard.ShowDialog();
}
}
public void Perform(FUIS.ActionProgress.INotificatable actionProgressNotificatable)
{
// Отримуємо файл із шаблоном
OID fileOID = _GetTemplateFile();
// Отримуємо все бібліотека сервера
var librariesOID = ObjectHolder.Session.ObjectDataManager.GetChildren(
new OID[] {Foss.FossDoc.ExternalModules.BusinessLogic.Schema.DocumentsLibraryStorage.Objects.OID},
new TPropertyTag[] {Foss.FossDoc.ExternalModules.BusinessLogic.Schema.DocumentsLibraryStorage.Attributes.Libraries.Tag},
new DS.TableRestriction(),
true, true);
// Якщо не знайдено, отже шукати нічого, просто виходимо
if (librariesOID == null || librariesOID.Length == 0)
return;
// Отримуємо папки у знайдених бібліотек
var foldersProp = ObjectHolder.Session.ObjectDataManager.GetProperties(
librariesOID,
Foss.FossDoc.ExternalModules.BusinessLogic.Schema.DocumentsLibrary.Attributes.Folders.Tag);
// Якщо не знайдено, отже шукати нічого, просто виходимо
if (foldersProp == null)
return;
// Діапазон дат "Від" і "До"
DateTime dateStart = _DateRangePage.StartDate;
DateTime dateFinish = _DateRangePage.FinishDate;
// Створюємо обмеження пошуку за датою створення.
// Це системне поле, його можна побачити в закладці "Об'єкт"
TableRestriction tableRestriction = new Converters.Restrictions.TableRestrictionHelper(
DS.resAND.ConstVal,
new Converters.Restrictions.PropertyRestrictionHelper(
Foss.FossDoc.ApplicationServer.ObjectDataManagment.Schema.PropertyTags.ObjectCreationTime,
dateStart,
DS.relopGE.ConstVal),
new Converters.Restrictions.PropertyRestrictionHelper(
Foss.FossDoc.ApplicationServer.ObjectDataManagment.Schema.PropertyTags.ObjectCreationTime,
dateFinish,
DS.relopLE.ConstVal));
string replacer = string.Empty;
for (int i = 0; i < foldersProp.Length; i++)
{
if (!foldersProp[i][0].PropertyTag.IsEquals(Foss.FossDoc.ExternalModules.BusinessLogic.Schema.DocumentsLibrary.Attributes.Folders.Tag))
continue;
var foldersOID = foldersProp[i][0].Value.GetMVoidVal();
for (int j = 0; j < foldersOID.Length; j++)
{
var documentsCount = ObjectHolder.Session.ObjectDataManager.GetChildrenCount(
foldersOID[j],
Foss.FossDoc.ExternalModules.BusinessLogic.Schema.Folder.Attributes.Documents.Tag,
tableRestriction);
var folderName = ObjectHolder.Session.ObjectDataManager.GetObjectDisplayName(foldersOID[j]);
string htmlRow = string.Format("<tr><td>{0}</td><td>{1}</td></tr>", folderName, documentsCount.ToString());
if(!string.IsNullOrWhiteSpace(replacer))
replacer+=Environment.NewLine;
replacer += htmlRow;
}
}
using (Foss.FossDoc.ApplicationServer.IO.StreamEx srcStream = ObjectHolder.Session.ObjectDataManager.UpdateBinaryStream(
fileOID,
Foss.FossDoc.ApplicationServer.Messaging.Schema.Attachment.PR_ATTACH_DATA_BIN,
Foss.FossDoc.ApplicationServer.ObjectDataManagment.BinaryStreamAccessMode.Read))
{
using (System.IO.StreamReader reader = new System.IO.StreamReader(srcStream))
{
var fileString = reader.ReadToEnd();
if (!string.IsNullOrWhiteSpace(replacer))
fileString = fileString.Replace("<!--BEGIN-->", replacer);
//Завантажуємо файл з типу документа в тимчасову папку:
//Формуємо тимчасову папку для зберігання файлів
System.IO.DirectoryInfo dirInfo = System.IO.Directory.CreateDirectory(System.IO.Path.Combine(System.IO.Path.GetTempPath(), Guid.NewGuid().ToString()));
string fullTemplateName = System.IO.Path.Combine(dirInfo.FullName, _FileTemplateName);
System.IO.File.WriteAllText(fullTemplateName, fileString, Encoding.UTF8);
System.Diagnostics.ProcessStartInfo p = new System.Diagnostics.ProcessStartInfo(fullTemplateName);
p.UseShellExecute = true;
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo = p;
process.Start();
}
}
}
private OID _GetTemplateFile()
{
OID templateFileOID = Converters.OID.Unspecified;
IObjectPropertyHolder propAttached = ObjectHolder[Foss.FossDoc.ExternalModules.BusinessLogic.Schema.Document.Attributes.AttachedFiles.Tag];
if (propAttached == null || !propAttached.Exists)
throw new ApplicationException("Не знайдено вкладених файлів");
OID[] attachedOIDs = propAttached.OIDs;
bool existTemplateFile = false;
for (int i = 0; i < attachedOIDs.Length; i++)
{
templateFileOID = attachedOIDs[i];
string fileName = ObjectHolder.Session.GetObjectsDisplayNames(templateFileOID)[0];
if (string.Compare(fileName, _FileTemplateName, true) == 0)
{
existTemplateFile = true;
break;
}
}
if (!existTemplateFile)
throw new ApplicationException("Не знайдено файл зі шаблоном Report.htm");
return templateFileOID;
}
public void BeginTransaction()
{
}
public void CommitTransaction()
{
}
public string Description
{
get
{
return string.Empty;
}
}
public void RollbackTransaction()
{
}
}
Як бачимо, додана одна кнопка "Побудувати звіт", а також реалізований інтерфейс Foss.FUIS.Wizard.Blocks.IAction.
Для реалізації завдання нам допоможе подія _ActionBuildReport_Perform, на яке ми підписалися при додаванні кнопки "Побудувати звіт".
Більшість звітів працюють з діапазоном часу, і в нашому звіті ми відобразимо майстер, в якому користувач введе необхідні дати. Саме для показу майстра ми виконали реалізацію особливого інтерфейсу.
EDMS.Plugins.ReportWizard.Pages.SelectRangeDatePage _DateRangePage;
void _ActionBuildReport_Perform(object sender, EventArgs e)
{
_DateRangePage= new EDMS.Plugins.ReportWizard.Pages.SelectRangeDatePage();
using (Foss.FossDoc.EDMS.Plugins.ReportWizard.Wizard wizard = new EDMS.Plugins.ReportWizard.Wizard(this, new FUIS.Wizard.Page[] { _DateRangePage }))
{
wizard.ShowDialog();
}
}
Наш звіт буде виводити результати у вигляді html-документа. Для цього створимо файл з ім'ям "Report.htm" такого змісту:
| <html>
<title>Звіт</title>
<body>
<table border="1px" width="100%">
<tr>
<td>Папка</td>
<td>Кількість документів</td>
</tr>
<!--BEGIN-->
</table>
</body>
</html>
|
Даний файл повинен бути приєднаний до документа-звіту. В момент, коли користувач натискає кнопку "Побудувати звіт", шаблон звіту буде завантажений в пам'ять, заповнений даними і відображений користувачеві як результат.
Використовуване API для реалізації звіту
Існує невеликий набір функцій, які корисні для програмного побудови звітів.
GetChildren
Дана функція відбирає будь-які об'єкти з заданих батьків із застосуванням умов-фільтрів:
OID[] GetChildren(
OID[] parentObjectOIDs,
TPropertyTag[] containerPropertyTags,
TableRestriction searchRestriction,
bool oneLevelOnly,
bool includeContainerPropertyTags
)
parentObjectOIDs - набір ідентифікаторів об'єктів-батьків, в яких необхідно проводити пошук об'єктів-дітей. Це може бути набір папок, де потрібно шукати документи.
containerPropertyTags - набір тегів-контейнерів (ієрархій), за якими необхідно проводити пошук батьків. Якщо ми шукаємо документи в папках, то слід вказати тег контейнера "Документи".
searchRestriction - умова відбору об'єктів-дітей.
oneLevelOnly - шукати дочірні об'єкти тільки на першому рівні. Значення false має сенс встановлювати тільки, якщо ми хочемо рекурсивно шукати об'єкти, наприклад папки в папках.
includeContainerPropertyTags - шукати дочірні об'єкти в контейнерах containerPropertyTags. Якщо false, то шукати у всіх контейнерах, крім зазначених.
Робота з TableRestriction
TableRestriction являє собою структуру, несучу умова пошуку (відбору) об'єктів. За допомогою неї можна відбирати документи в папках із заданим значенням полів в будь-яких варіаціях, підтримується комбінування умов AND-OR-NOT.
Для більш спрощеною роботи рекомендується використовувати допоміжні класи в просторі Foss.FossDoc.ApplicationServer.Converters.Restrictions.
Умови-контейнери AND-OR-NOT
Клас TableRestrictionHelper отримує в конструктор аргумент int restrictionType, який визначає типізацію умови.
Допустимі значення restrictionType визначають вміст умов:
- DS.resAND.ConstVal, DS.resOR.ConstVal, DS.resNOT.ConstVal - характеризує, яким чином між собою комбінуються дочірні об'єкти TableRestriction. Для випадку NOT дочірнє умова має бути єдиним.
- DS.resProperty.ConstVal -дочернее условие - это PropertyRestriction.
- DS.resSubRestriction.ConstVal - дочернее условие - это SubRestriction.
Умова для властивостей об'єктів PropertyRestriction
Найбільш часто застосовується умова для пошуку об'єктів (документів) - за значенням полів. Наприклад, якщо вам потрібно відібрати документи, де в полі "Хто готував" встановлено певний співробітник (тобто поле "Хто готував" дорівнює "Петров"), то для вирішення такого завдання слід використовувати PropertyRestriction.
Для зручності створення примірників PropertyRestriction використовується клас PropertyRestrictionHelper, який містить безліч конструкторів для різних типів даних.
Загальний принцип простий: перший аргумент - це тег властивості, другий - значення, з яким порівнювати, і третій - режим порівняння. Нижче наводиться конструктор для випадку роботи із строковими даними:
PropertyRestrictionHelper(DS.TPropertyTag tag, string value, int relop)
Аргумент relop приймає такі значення:
DS.relopGE.ConstVal - "Більше або дорівнює"
DS.relopEQ.ConstVal - "Дорівнює"
DS.relopGT.ConstVal - "Більше"
DS.relopLE.ConstVal - "Менше або дорівнює"
DS.relopLT.ConstVal - "Менше"
DS.relopNE.ConstVal - "Не дорівнює"
DS.relopRE.ConstVal - "Підрядок" (пошук підрядка, тільки для строкових даних)
Якщо передбачається використовувати складну умову, то може знадобитися "обернути" PropertyRestrictionHelper в TableRestrictionHelper таким чином:
PropertyRestrictionHelper propDispName = new PropertyRestrictionHelper(Foss.FossDoc.ApplicationServer.ObjectDataManagment.Schema.PropertyTags.DisplayName, "Ім'я об'єкта", DS.relopEQ.ConstVal);
TableRestrictionHelper tblProperty = new TableRestrictionHelper(DS.resProperty.ConstVal, propDispName);
В даному прикладі створено умова для пошуку об'єкта з властивістю "Ім'я" (тег Display name), і його можна використовувати в GetChildren. Ми створили екземпляр PropertyRestrictionHelper propDispName, а потім передали його в конструктор TableRestrictionHelper, де вказали, що всередині буде обмеження по властивості.
За допомогою такого підходу ви можете комбінувати різні умови. Для простоти пояснення пропонуємо наступну схему, яка використана в прикладі, де ми шукали документи за вказаний період часу:
Слід розуміти, що тип даних "Дата-час" а також "Дата" містить в собі компоненту "час" в будь-якому випадку. Тому не рекомендується використовувати умови в стилі "Дата дорівнює значення", так як є ризик не отримати необхідні документи через неточний відповідності умовам пошуку. Ми рекомендуємо для полів дата-час використовувати умови пошуку "більше ніж" і "менше ніж".