Table of Contents

Сводный отчет с расположение точек учета в колонках

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

ШАГ 1. Откройте копию системной отчетной формы Параметры потребления по точкам учета. Удалите из нее все секции, группировку и таблицу из секции detailBand1. Добавьте секции: заголовок отчета с именем ReportHeader, заголовок группы с именем PageHeader, подвал отчета с именем ReportFooter, переименуйте секцию detailBand1 на Detail. Добавьте таблицу из 5 строк в секцию ReportHeader и таблицы из одной строки в секции PageHeader, Detail и ReportFooter Для всех ячеек в колонках кроме первой, задайте свойство Видимость = Нет, чтобы не отображать колонки, которые не будут связаны с точками учета.

Задайте следующие наименования строк в таблицах (код скриптов привязан к этим наименованиям): measurePointTitleRow - для строки Точка учета в таблице секции ReportHeader measurePointAddressRow - для строки Адрес в таблице секции ReportHeader deviceSerialNumberRow - для строки № прибора учета в таблице секции ReportHeader deviceModelRow - - для строки Модель прибора учета в таблице секции ReportHeader startEndTotalsRow - для строки Показания интеграторов на начало и конец отчетного периода в таблице секции ReportHeader pageHeaderRow - для строки Дата в таблице секции PageHeader detailRow - для строки DataDate в таблице секции Detail footerRow - для строки Итого в таблице секции ReportFooter

ШАГ 2. Откройте вкладку Скрипты и скопируйте в нее следующий код:

Код скриптов


// Скрипт для изменения структуры источника данных позволяющий отображать точки учета в колонках

using System.Data;
using System.Collections.Generic;
using System.Linq;

// Количество отображаемых точек учета в данной отчетной форме не может быть больше 2, а количество параметров - 2.
// Чтобы увеличить, надо добавить колонки в отчетную форму.
// Для добавляемых колонок надо задать свойство Видимость = Нет, чтобы скрывать колонки, если их количество больше числа выбранных точек учета

// Наименование отображаемых параметров потребления (из узла Параметры потребления в Списке полей)
// ВАЖНО!!! Размер массива должен быть согласован с количеством ячеек в строках detailRow и footerRow
private string[] CONSUMPTION_FIELD_NAMES = new string[] {"M1", "M2"};

// Наименование параметров с показаниями интеграторов (из узла Показания интеграторы на начало и конец отчетного периода в Списке полей)
// ВАЖНО!!! Размер массива должен быть согласован с количеством ячеек в строке startEndTotalsRow
private string[] TOTALS_FIELD_NAMES = new string[] {"M1_Start", "M1_End", "M2_Start", "M2_End"};

// Экземпляр отчетной формы
private XtraReport _xtraReport;

// Рабочая таблица с данными для секции ReportHeader
private DataTable _wrkHeaderDataTable;

// Рабочая таблица с данными для секции Detail
private DataTable _wrkDetailDataTable;

// Рабочая табочая таблица с данными для секции ReportFooter
private DataTable _wrkFooterDataTable;

// Наименования колонок в рабочих таблицах формируются динамически 
// в зависимости от значения присваемому полям _number, _consumption и _totals
private string _number, _consumption, _totals;

// Наименование колонок в создаваемых рабочих таблицах
private string Title_Number { get { return "Title_" + _number; } }
private string Address_Number { get { return "Address_" + _number; } }
private string SerialNumber_Number { get { return "SerialNumber_" + _number; } }    
private string DeviceModel_Number { get { return "DeviceModel_" + _number; } }    
private string Value_Number { get { return "Value_" + _consumption + "_" + _number; } }   
private string Totals_Number { get { return "Totals_" + _totals + "_"  + _number; } }   

// Тип данных, по которым формируется отчет
private int _dataType = 64;

// Параметры выбранных точек учета (ключ - номер точки учета)
private Dictionary<int, MeasurePointInfo> _measurePoints = new Dictionary<int, MeasurePointInfo>();

// Параметры точки учета используемые в секции ReportHeader
private class MeasurePointInfo
{
    // Параметры точки учета
    public string Title {get; set;}
    public string Address {get; set;}
    public string DeviceModel {get; set;}
    public string SerialNumber {get; set;}
    public int MeasurePointId {get; set;}
    
    // Берется из пользовательского атрибута с кодом ValueForSorting
    // Используется для задания порядка следования колонок с точками учета
    // При необходимости сортировку можно задать по любому параметру точки учета (см. строки 131 и 156), закомментировав строку 139.
    public object ValueForSorting {get; set;}

    // Показания интеграторов на начало и конец отчетного периода
    public Dictionary<string, double?> Totals_Value {get; set;}
}

// В обработчике BaseReport_DataSourceDemanded() создаются рабочие таблицы источника данных и к ним привязываются ячейки отчетной формы
private void BaseReport_DataSourceDemanded(object sender, System.EventArgs e) 
{
    _xtraReport = (XtraReport)sender;
    var dataSet = (DataSet)_xtraReport.DataSource;
    if (dataSet == null)
    {
        return;
    }
    
    // Тип данных
    _dataType = (int)_xtraReport.Parameters["DATA_TYPE"].Value;
 
    // Заполняем информацию по точкам учета, используемую в шапке отчета
    FillMeasurePointInfo(dataSet);
   
    // Создаем рабочие таблицы и добавляем их в dataSet (источник данных отчета)
    CreateWorkTables(dataSet);

    // Привязываем ячейки к полям рабочих таблиц       
    BindCellsToWrkTables();

    // Заполняем рабочие таблицы значениями задаваемых параметров потребления
    FillWorkTables(dataSet);
      
    // Меняем имя таблицы, из которой берутся данные для секции Detail.
    // Данные будут браться из рабочей таблицы содержащей в каждой строке с меткой времени параметры всех точек учета выбранных при формировании отчета.
    _xtraReport.DataMember = "DetailDataTable"; 
}

// Формирует отсортированный Dictionary точек учета (в _measurePoints ключ - номер точки учета).
// Сортировка точек учета выполняется по значению свойства MeasurePointInfo.ValueForSorting
private void FillMeasurePointInfo(DataSet dataSet)
{
    // Исходная таблица c параметрами точек учета
    var measurePointTable = dataSet.Tables["MeasurePointTable"];
    measurePointTable.PrimaryKey = new DataColumn[1] { measurePointTable.Columns["MeasurePointId"] };

    // Исходная таблица с пользовательскими атрибутами точек учета
    var attributeTable = dataSet.Tables["MeasurePointAttributeTable"];
    attributeTable.PrimaryKey = new DataColumn[1] { attributeTable.Columns["MeasurePointId"]};

    // Исходная таблица с показаниями интеграторов на начало и конец отчетного периода
    var startEndTotalsTable = dataSet.Tables["StartEndTotalsTable"];
    startEndTotalsTable.PrimaryKey = new DataColumn[1] { startEndTotalsTable.Columns["MeasurePointId"]};

    // Формируем Dictionary c параметрами точек учета для шапки отчета
    foreach(DataRow row in measurePointTable.Rows)
    {
        int measurePointId = (int)row["MeasurePointId"];
        int number = (int)row["Number"];
        
        // Получаем строку с пользовательскими атрибутами для текущей точки учета
        var attributeRow = attributeTable.Rows.Find(new object[] { measurePointId });

        // Добавляем строку в Dictionary c информацией по точкам учета используемой в шапке отчета
        _measurePoints[number] = new MeasurePointInfo()
        {        
                Title = (string)row["MeasurePointTitle"],
                Address = Convert.ToString(row["Address"]),
                SerialNumber = Convert.ToString(row["SerialNumber"]),
                DeviceModel = Convert.ToString(row["DeviceModel"]),
                MeasurePointId = (int)row["MeasurePointId"],
                ValueForSorting = int.MaxValue // Если у точки учета атрибут для сортировки не задан, то она будет в последних колонках
        };

        if (attributeRow != null)
        {
            if (attributeTable.Columns.Contains("ValueForSorting") && attributeRow["ValueForSorting"] != DBNull.Value)
            {
                // Задаем свойство, которое определяет порядок следования колонок с точками учета
                _measurePoints[number].ValueForSorting = Convert.ToInt32(attributeRow["ValueForSorting"]);
            }
        }
            
        // Показания интеграторов на начало и конец отчетного периода
        var startEndTotalsRow = startEndTotalsTable.Rows.Find(measurePointId);
        if (startEndTotalsRow != null)
        {
            _measurePoints[number].Totals_Value = new Dictionary<string, double?>();
            foreach(string fieldName in TOTALS_FIELD_NAMES)
            {
                _measurePoints[number].Totals_Value[fieldName] = startEndTotalsRow[fieldName] != DBNull.Value ? (double?)startEndTotalsRow[fieldName] : null;
            }    
        }
    }
    
    // Сортировка точек учета по значению свойства MeasurePointInfo.ValueForSorting
    _measurePoints = _measurePoints.OrderBy(x => x.Value.ValueForSorting).ToDictionary(x => x.Key, x => x.Value);
}

// Создает рабочие таблицы.
private void CreateWorkTables(DataSet dataSet)
{
    // Таблица из одной строки со свойствами точки учета
    // К колонкам этой таблицы будут привязаны ячейки секции ReportHeader	
    _wrkHeaderDataTable = dataSet.Tables["HeaderDataTable"];
    
    // Проверка нужна для возможности повторных запусков из редактор отчетов
    if (_wrkHeaderDataTable == null)
    {
        _wrkHeaderDataTable = new DataTable("HeaderDataTable");
        dataSet.Tables.Add(_wrkHeaderDataTable);
    }
    _wrkHeaderDataTable.Columns.Clear();

    // Таблица содержащая количество строк равное количеству меток времени
    // К колонкам этой таблицы будут привязаны ячейки секции Detail.	
    // В каждой строке с меткой времени содержатся параметры всех точек учета выбранных при формировании отчета.
    _wrkDetailDataTable = dataSet.Tables["DetailDataTable"];
    
    // Проверка нужна для возможности повторных запусков из редактор отчетов
    if (_wrkDetailDataTable == null)
    {
        _wrkDetailDataTable = new DataTable("DetailDataTable");
        dataSet.Tables.Add(_wrkDetailDataTable);
    }
    _wrkDetailDataTable.Columns.Clear();
    _wrkDetailDataTable.Columns.Add("DataDate", typeof(DateTime));

    // Таблица из одной строки с итогами по точкам учета.
    // К колонкам этой таблицы будут привязаны ячейки секции ReportFooter
    _wrkFooterDataTable = dataSet.Tables["FooterDataTable"];
    
    // Проверка нужна для возможности повторных запусков из редактор отчетов
    if (_wrkFooterDataTable == null)
    {
        _wrkFooterDataTable = new DataTable("FooterDataTable");
        dataSet.Tables.Add(_wrkFooterDataTable);
    }
    _wrkFooterDataTable.Columns.Clear();

    // Формируем структуру рабочих таблиц
    foreach(int number in _measurePoints.Keys)
    {
        // Используется при получении наименований колонок
        _number = number.ToString();
        
        // Таблица с данными для шапки отчета
        _wrkHeaderDataTable.Columns.Add(Title_Number, typeof(string));
        _wrkHeaderDataTable.Columns.Add(Address_Number, typeof(string));
        _wrkHeaderDataTable.Columns.Add(SerialNumber_Number, typeof(string));
        _wrkHeaderDataTable.Columns.Add(DeviceModel_Number, typeof(string));
        // Поля для показаний интеграторов на начало и конец отчетного периода в шапке отчета
        foreach(string fieldName in TOTALS_FIELD_NAMES)
        {
            // Используется при получении наименований колонок
            _totals = fieldName;
            
            _wrkHeaderDataTable.Columns.Add(Totals_Number, typeof(double));
        }    
            
        // Таблицы с данными для тела и подвала отчета 
        foreach(string fieldName in CONSUMPTION_FIELD_NAMES)
        {
            // Используется при получении наименований колонок
            _consumption = fieldName;
            
            _wrkDetailDataTable.Columns.Add(Value_Number, typeof(double));
            _wrkFooterDataTable.Columns.Add(Value_Number, typeof(double));
        }    
    }
}

// Привязываем ячейки к полям рабочих таблиц
private void BindCellsToWrkTables()
{
    // Настраиваем привязку и формат для меток времени
    detailRow.Cells[0].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[DetailDataTable].[DataDate]"));
    detailRow.Cells[0].TextFormatString = "{0:dd.MM.yyyy}";           // суточные данные
    if (_dataType == 128)            
    {
        detailRow.Cells[0].TextFormatString = "{0:dd.MM.yyyy HH:mm}";  // часовые данные
    }
    else if (_dataType == 32)
    {
        detailRow.Cells[0].TextFormatString = "{0:dd MMMM yyyy}";     // месячные данные
    }

    // Формат вывода чисовых значений
    string valueFormat = "{0:F2}";
    
    // Номер колонки в строке шапки отчета
    int k = 1;
    
    // Сдвиг номеров колонок 
    int shiftTotals = 1, shiftConsumption = 1;
    
    // Привязываем ячейки к полям рабочих таблиц для заданных точек учета
    foreach(var number in _measurePoints.Keys)
    {
        // Количество отображаемых точек учета определяется количеством колонок в данной отчетной форме.
        // Чтобы увеличить, надо добавить колонки в отчетную форму
        if (k + shiftTotals > startEndTotalsRow.Cells.Count)
        {
            break;
        }
        
        // Наименование отображаемого параметра
        int i = 0;
        foreach(string fieldName in CONSUMPTION_FIELD_NAMES)			
        {
            pageHeaderRow.Cells[shiftConsumption + i].Text = fieldName;
            pageHeaderRow.Cells[shiftConsumption + i].Visible = true;
            i++;
        }
        
        // Используется при получении наименований колонок
        _number = number.ToString();
        
        measurePointTitleRow.Cells[k].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[HeaderDataTable].[" + Title_Number + "]"));
        measurePointTitleRow.Cells[k].Visible = true;
        
        measurePointAddressRow.Cells[k].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[HeaderDataTable].[" + Address_Number + "]"));
        measurePointAddressRow.Cells[k].Visible = true;
        
        deviceSerialNumberRow.Cells[k].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[HeaderDataTable].[" + SerialNumber_Number + "]"));
        deviceSerialNumberRow.Cells[k].Visible = true;
        
        deviceModelRow.Cells[k].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[HeaderDataTable].[" + DeviceModel_Number + "]"));
        deviceModelRow.Cells[k].Visible = true;

        int j = 0;
        foreach(string fieldName in TOTALS_FIELD_NAMES)			
        {
            _totals = fieldName;
            startEndTotalsRow.Cells[shiftTotals + j].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[HeaderDataTable].[" + Totals_Number + "]"));	
            startEndTotalsRow.Cells[shiftTotals + j].Visible = true;
            startEndTotalsRow.Cells[shiftTotals + j].TextFormatString = fieldName + " " + valueFormat;
            j++;
        }    

        j = 0;
        foreach(string fieldName in CONSUMPTION_FIELD_NAMES)			
        {
            _consumption = fieldName;
            detailRow.Cells[shiftConsumption + j].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[DetailDataTable].[" + Value_Number + "]"));				
            detailRow.Cells[shiftConsumption + j].TextFormatString = valueFormat;
            detailRow.Cells[shiftConsumption + j].Visible = true;
            
            footerRow.Cells[shiftConsumption + j].ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", "[FooterDataTable].[" + Value_Number + "]"));				
            footerRow.Cells[shiftConsumption + j].TextFormatString = valueFormat;
            footerRow.Cells[shiftConsumption + j].Visible = true;

            j++;
        }    

        shiftTotals = shiftTotals + TOTALS_FIELD_NAMES.Length;
        shiftConsumption = shiftConsumption + CONSUMPTION_FIELD_NAMES.Length;
        k++;
    }
}

// Заполняет рабочие таблицы данными для отчета
private void FillWorkTables(DataSet dataSet)
{
    // Исходная таблица c параметрами потребления точек учета
    var consumptionTable = dataSet.Tables["ConsumptionTable"];
    consumptionTable.PrimaryKey = new DataColumn[2] { consumptionTable.Columns["MeasurePointId"], consumptionTable.Columns["DataDate"] };

    var headerDataRow = _wrkHeaderDataTable.NewRow();
    _wrkHeaderDataTable.Rows.Add(headerDataRow);
      
    var footerDataRow = _wrkFooterDataTable.NewRow();
    _wrkFooterDataTable.Rows.Add(footerDataRow);

    var data = (DateTime)_xtraReport.Parameters["DATE_START"].Value;
    
    _wrkDetailDataTable.Rows.Clear();

    // Заполняем строки таблиц связанных с ячеками секций DetailReportHeader, DetailReportData и DetailReportFooter 
    // В КАЖДОЙ СТРОКЕ с меткой времени заполняем данные для ВСЕХ заданных точек учета   
    while (data <= (DateTime)_xtraReport.Parameters["DATE_END"].Value)
    {
        var detailRow = _wrkDetailDataTable.NewRow();
        _wrkDetailDataTable.Rows.Add(detailRow);

        // Проходим по ВСЕМ заданным точкам учета       
        foreach(int number in _measurePoints.Keys)
        {
            _number = number.ToString();
            int measurePointId = _measurePoints[number].MeasurePointId;
            
            headerDataRow[Title_Number] = _measurePoints[number].Title;
            headerDataRow[Address_Number] = _measurePoints[number].Address;
            headerDataRow[SerialNumber_Number] = (object)_measurePoints[number].SerialNumber ?? DBNull.Value;
            headerDataRow[DeviceModel_Number] = (object)_measurePoints[number].DeviceModel ?? DBNull.Value;

            // Заполняем метку времени
            detailRow["DataDate"] = data;
            
            foreach(string fieldName in TOTALS_FIELD_NAMES)
            {
                _totals = fieldName;
                headerDataRow[Totals_Number] = (object)_measurePoints[number].Totals_Value[_totals] ?? DBNull.Value;
            }    

            // Заполняет сроку с потреблениями и накапливает сумму для итогов
            var row = consumptionTable.Rows.Find(new object[] { measurePointId, data});
            if (row != null)
            {
                foreach(string fieldName in CONSUMPTION_FIELD_NAMES)
                {
                    _consumption = fieldName;
                    // Значение, которое отображается в теле отчета
                    var value = (object)row[fieldName] ?? DBNull.Value;
                
                    if (value != DBNull.Value)
                    {
                        detailRow[Value_Number] = (double)value;
	                
                        // Суммирование итогов по каждой колонке
                        if (footerDataRow[Value_Number] == DBNull.Value)
                        {
                            footerDataRow[Value_Number] = 0;
                        }
                        footerDataRow[Value_Number] = (double)footerDataRow[Value_Number] + (double)value;
                    }
                }
            }
        }
	  
	  // Переход к следующей метки времени    
        if (_dataType == 32)
        {
            data = data.AddMonths(1);
        }  
        else if (_dataType == 128)
        {
            data = data.AddHours(1);
        }  
        else
        {
            data = data.AddDays(1);
        }  
    }
}

ШАГ 3. Переключитесь на вкладку Обозреватель отчета и для узла MeasurePointSummaryReport в окне свойств задайте скрипт Запрос источника данных равным BaseReport_DataSourceDemanded.

ШАГ 4. Сохраните изменения.

ШАГ 5. Пример сформированного отчета с отображением точек учета в колонках: