Программирование отчетов в FossLook

В данной статье мы изучим возможности FossLook по программированию отчетов. Отчет - это по сути визуализация поиска (отбора) документов, предназначенная для печати. Для примера мы создадим свой тип документа с одной кнопкой "Построить отчет". При нажатии на кнопку будут выполнены отбор документов и представление их в виде таблицы.

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

Материалы для загрузки

Загрузите подготовленные нами материалы для создания отчетов:

  • Проект-пример под Visual Studio. Скачайте, откройте исходный код класса Form.cs для изучения.
  • Библиотека-пример с отчетом. Скачайте и импортируйте в систему. Библиотека содержит необходимый тип документа и исходный код в его классе карточки. Сам документ-отчет создайте вручную в папке "Отчеты".

Реализация отчета

После импорта библиотеки-примера вы можете создать в ней один документ, который собственно и будет отчетом. Во вложенных файлах данного документа необходимо создать файл Report.htm, содержимое которого будет рассмотрено ниже.

Документ FossLook

С точки зрения реализации для построения отчета достаточно реализовать отбор документов по условиям и вывод их в некотором виде (в 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, где указали, что внутри будет ограничение по свойству.

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

Схема поискового условия TableRestriction FossLook

Следует понимать, что тип данных "Дата-время" а также "Дата" содержит в себе компоненту "время" в любом случае. Поэтому не рекомендуется использовать условия в стиле "Дата равно значение", так как есть риск не получить необходимые документы из-за неточного соответствия условиям поиска. Мы рекомендуем для полей дата-время использовать условия поиска "больше чем" и "меньше чем".

© 2001-2017 ФОСС-Он-Лайн. Все права защищены.