Dynamic Tree Report hazırlandı
This commit is contained in:
parent
662edaace6
commit
2118acf6e1
4 changed files with 818 additions and 6 deletions
|
|
@ -0,0 +1,790 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DevExpress.DataAccess.Json;
|
||||||
|
using DevExpress.Drawing;
|
||||||
|
using DevExpress.XtraPrinting;
|
||||||
|
using DevExpress.XtraReports.UI;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Localization;
|
||||||
|
using Sozsoft.Platform;
|
||||||
|
using Sozsoft.Platform.DynamicData;
|
||||||
|
using Sozsoft.Platform.Entities;
|
||||||
|
using Sozsoft.Platform.Enums;
|
||||||
|
using Sozsoft.Platform.ListForms;
|
||||||
|
using Sozsoft.Platform.Localization;
|
||||||
|
using Sozsoft.Platform.Queries;
|
||||||
|
|
||||||
|
namespace Sozsoft.Reports.PredefinedReports;
|
||||||
|
|
||||||
|
public class DynamicTreeReport : XtraReport
|
||||||
|
{
|
||||||
|
public const string ReportName = nameof(DynamicTreeReport);
|
||||||
|
private const int A4PortraitWidth = 827;
|
||||||
|
private const int HorizontalMargin = 35;
|
||||||
|
private const int LandscapeColumnThreshold = 5;
|
||||||
|
private const string TreeLevelColumn = "__TreeLevel";
|
||||||
|
private const string TreeHasChildrenColumn = "__TreeHasChildren";
|
||||||
|
private const string TreePrefixColumn = "__TreePrefix";
|
||||||
|
private const string TreeDataSourceName = "DynamicTree";
|
||||||
|
private static readonly System.Drawing.Color TableBorderColor = System.Drawing.Color.FromArgb(226, 232, 240);
|
||||||
|
private static IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public static void SetServiceProvider(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<DynamicTreeReport> CreateAsync(IDictionary<string, string> parameters)
|
||||||
|
{
|
||||||
|
var report = new DynamicTreeReport();
|
||||||
|
await report.BuildAsync(parameters);
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DynamicTreeReport()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task BuildAsync(IDictionary<string, string> parameters)
|
||||||
|
{
|
||||||
|
if (_serviceProvider == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("DynamicTreeReport service provider is not configured.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parameters.TryGetValue("listFormCode", out var listFormCode) || string.IsNullOrWhiteSpace(listFormCode))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("listFormCode parameter is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
using var scope = _serviceProvider.CreateScope();
|
||||||
|
var authManager = scope.ServiceProvider.GetRequiredService<IListFormAuthorizationManager>();
|
||||||
|
var listFormManager = scope.ServiceProvider.GetRequiredService<IListFormManager>();
|
||||||
|
var listFormFieldManager = scope.ServiceProvider.GetRequiredService<IListFormFieldManager>();
|
||||||
|
var customizationManager = scope.ServiceProvider.GetRequiredService<IListFormCustomizationManager>();
|
||||||
|
var dynamicDataManager = scope.ServiceProvider.GetRequiredService<IDynamicDataManager>();
|
||||||
|
var selectQueryManager = scope.ServiceProvider.GetRequiredService<ISelectQueryManager>();
|
||||||
|
var defaultValueHelper = scope.ServiceProvider.GetRequiredService<DefaultValueHelper>();
|
||||||
|
var localizer = scope.ServiceProvider.GetRequiredService<IStringLocalizer<PlatformResource>>();
|
||||||
|
var configuration = scope.ServiceProvider.GetRequiredService<IConfiguration>();
|
||||||
|
|
||||||
|
if (!await authManager.CanAccess(listFormCode))
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException($"No read permission for list form '{listFormCode}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var listForm = await listFormManager.GetUserListForm(listFormCode);
|
||||||
|
var treeOptions = GetTreeOptions(listForm);
|
||||||
|
if (string.IsNullOrWhiteSpace(treeOptions?.KeyExpr) || string.IsNullOrWhiteSpace(treeOptions.ParentIdExpr))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Tree options are not configured for list form '{listFormCode}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var fields = (await listFormFieldManager.GetUserListFormFields(listFormCode))
|
||||||
|
.Where(x => x.CanRead)
|
||||||
|
.OrderBy(x => x.ListOrderNo ?? int.MaxValue)
|
||||||
|
.ThenBy(x => x.CaptionName)
|
||||||
|
.ToList();
|
||||||
|
var customizations = await customizationManager.GetUsersServerCustomizations(listFormCode);
|
||||||
|
var (dynamicDataRepository, connectionString, dataSourceType) = await dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode);
|
||||||
|
|
||||||
|
var queryParams = new QueryParameters
|
||||||
|
{
|
||||||
|
ListFormCode = listFormCode,
|
||||||
|
Filter = GetParameter(parameters, "filter"),
|
||||||
|
Sort = GetParameter(parameters, "sort"),
|
||||||
|
Group = GetParameter(parameters, "group"),
|
||||||
|
GroupSummary = GetParameter(parameters, "groupSummary"),
|
||||||
|
TotalSummary = GetParameter(parameters, "totalSummary"),
|
||||||
|
Take = 0,
|
||||||
|
Skip = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
selectQueryManager.PrepareQueries(listForm, fields, dataSourceType, customizations, queryParams);
|
||||||
|
var selectFieldsByName = DynamicReportImageHelper.CreateSelectFieldMap(selectQueryManager.SelectFields);
|
||||||
|
var editorTypesByField = DynamicReportImageHelper.CreateEditorTypeMap(listForm);
|
||||||
|
|
||||||
|
var visibleFields = fields
|
||||||
|
.Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined")
|
||||||
|
.Where(x => x.IsActive != false && x.Visible == true)
|
||||||
|
.Where(x => selectFieldsByName.ContainsKey(x.FieldName))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (visibleFields.Count == 0)
|
||||||
|
{
|
||||||
|
visibleFields = fields
|
||||||
|
.Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined")
|
||||||
|
.Where(x => x.IsActive != false)
|
||||||
|
.Where(x => selectFieldsByName.ContainsKey(x.FieldName))
|
||||||
|
.Take(8)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var reportColumns = visibleFields
|
||||||
|
.Select(field =>
|
||||||
|
{
|
||||||
|
var selectField = selectFieldsByName[field.FieldName];
|
||||||
|
editorTypesByField.TryGetValue(field.FieldName, out var editorType);
|
||||||
|
return CreateReportColumn(
|
||||||
|
field,
|
||||||
|
selectField,
|
||||||
|
defaultValueHelper,
|
||||||
|
editorType,
|
||||||
|
DynamicReportImageHelper.IsImageEditor(editorType),
|
||||||
|
editorType == PlatformConsts.EditorTypes.dxHtmlEditor);
|
||||||
|
})
|
||||||
|
.Where(x => !string.IsNullOrWhiteSpace(x.BindingName) && x.BindingName != "undefined")
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var selectQuery = DynamicReportImageHelper.StripReportPaging(DynamicReportImageHelper.ApplyQueryParameters(
|
||||||
|
selectQueryManager.SelectQuery,
|
||||||
|
selectQueryManager.SelectQueryParameters));
|
||||||
|
selectQuery = DynamicReportSqlBuilder.ApplyLookupTextColumns(
|
||||||
|
selectQuery,
|
||||||
|
reportColumns,
|
||||||
|
defaultValueHelper,
|
||||||
|
dataSourceType);
|
||||||
|
|
||||||
|
var imageBaseUrl = DynamicReportImageHelper.GetImageBaseUrl(configuration);
|
||||||
|
var sourceRows = await ReadRowsAsync(dynamicDataRepository, selectQuery, connectionString);
|
||||||
|
var orderedRows = OrderTreeRows(sourceRows, treeOptions).ToList();
|
||||||
|
reportColumns = ApplyImageCounts(reportColumns, orderedRows, imageBaseUrl);
|
||||||
|
|
||||||
|
ConfigurePage(reportColumns);
|
||||||
|
ConfigureDataSource(orderedRows, reportColumns);
|
||||||
|
BuildLayout(listForm, reportColumns, localizer, imageBaseUrl);
|
||||||
|
RequestParameters = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma warning disable CA1416
|
||||||
|
private void ConfigurePage(IReadOnlyCollection<ReportColumn> visibleFields)
|
||||||
|
{
|
||||||
|
Margins = CreateReportMargins(HorizontalMargin, HorizontalMargin, 35, 35);
|
||||||
|
PaperKind = DevExpress.Drawing.Printing.DXPaperKind.A4;
|
||||||
|
var portraitContentWidth = A4PortraitWidth - Margins.Left - Margins.Right;
|
||||||
|
var requestedContentWidth = visibleFields.Sum(x => x.IsImage ? GetImageColumnWidth(x) : GetPreferredColumnWidth(x.Field));
|
||||||
|
Landscape = visibleFields.Count >= LandscapeColumnThreshold || requestedContentWidth > portraitContentWidth;
|
||||||
|
Font = new DXFont("Arial", 8.25F);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static System.Drawing.Printing.Margins CreateReportMargins(
|
||||||
|
int left,
|
||||||
|
int right,
|
||||||
|
int top,
|
||||||
|
int bottom)
|
||||||
|
{
|
||||||
|
return new System.Drawing.Printing.Margins(left, right, top, bottom);
|
||||||
|
}
|
||||||
|
#pragma warning restore CA1416
|
||||||
|
|
||||||
|
private static async Task<List<Dictionary<string, object>>> ReadRowsAsync(
|
||||||
|
IDynamicDataRepository dynamicDataRepository,
|
||||||
|
string selectQuery,
|
||||||
|
string connectionString)
|
||||||
|
{
|
||||||
|
var rows = await dynamicDataRepository.QueryAsync(selectQuery, connectionString);
|
||||||
|
return rows
|
||||||
|
.OfType<IDictionary<string, object>>()
|
||||||
|
.Select(row => row.ToDictionary(x => x.Key, x => x.Value, StringComparer.OrdinalIgnoreCase))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Dictionary<string, object>> OrderTreeRows(
|
||||||
|
IReadOnlyList<Dictionary<string, object>> rows,
|
||||||
|
TreeOptionDto treeOptions)
|
||||||
|
{
|
||||||
|
var childrenByParent = rows
|
||||||
|
.GroupBy(row => NormalizeTreeKey(GetRowValue(row, treeOptions.ParentIdExpr)))
|
||||||
|
.ToDictionary(x => x.Key, x => x.ToList(), StringComparer.OrdinalIgnoreCase);
|
||||||
|
var visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var rootKey = NormalizeTreeKey(treeOptions.RootValue);
|
||||||
|
var roots = childrenByParent.TryGetValue(rootKey, out var configuredRoots)
|
||||||
|
? configuredRoots
|
||||||
|
: rows.Where(row => IsRootValue(GetRowValue(row, treeOptions.ParentIdExpr), treeOptions.RootValue)).ToList();
|
||||||
|
|
||||||
|
foreach (var row in Traverse(roots, childrenByParent, treeOptions.KeyExpr, 0, visited))
|
||||||
|
{
|
||||||
|
yield return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var row in rows)
|
||||||
|
{
|
||||||
|
var key = NormalizeTreeKey(GetRowValue(row, treeOptions.KeyExpr));
|
||||||
|
if (visited.Contains(key))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var orphan in Traverse([row], childrenByParent, treeOptions.KeyExpr, 0, visited))
|
||||||
|
{
|
||||||
|
yield return orphan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Dictionary<string, object>> Traverse(
|
||||||
|
IEnumerable<Dictionary<string, object>> rows,
|
||||||
|
IReadOnlyDictionary<string, List<Dictionary<string, object>>> childrenByParent,
|
||||||
|
string keyExpr,
|
||||||
|
int level,
|
||||||
|
ISet<string> visited)
|
||||||
|
{
|
||||||
|
foreach (var row in rows)
|
||||||
|
{
|
||||||
|
var key = NormalizeTreeKey(GetRowValue(row, keyExpr));
|
||||||
|
if (!visited.Add(key))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasChildren = childrenByParent.TryGetValue(key, out var children) && children.Count > 0;
|
||||||
|
row[TreeLevelColumn] = level;
|
||||||
|
row[TreeHasChildrenColumn] = hasChildren;
|
||||||
|
row[TreePrefixColumn] = $"{new string(' ', level * 4)}{(hasChildren ? "[-] " : " ")}";
|
||||||
|
yield return row;
|
||||||
|
|
||||||
|
if (!hasChildren)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var child in Traverse(children, childrenByParent, keyExpr, level + 1, visited))
|
||||||
|
{
|
||||||
|
yield return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsRootValue(object parentValue, object rootValue)
|
||||||
|
{
|
||||||
|
if (rootValue == null)
|
||||||
|
{
|
||||||
|
return parentValue == null ||
|
||||||
|
parentValue == DBNull.Value ||
|
||||||
|
string.IsNullOrWhiteSpace(Convert.ToString(parentValue, CultureInfo.InvariantCulture));
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Equals(
|
||||||
|
NormalizeTreeKey(parentValue),
|
||||||
|
NormalizeTreeKey(rootValue),
|
||||||
|
StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object GetRowValue(IDictionary<string, object> row, string fieldName) =>
|
||||||
|
DynamicReportImageHelper.TryGetRowValue(row, fieldName, out var value) ? value : null;
|
||||||
|
|
||||||
|
private static string NormalizeTreeKey(object value)
|
||||||
|
{
|
||||||
|
if (value == null || value == DBNull.Value)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Convert.ToString(value, CultureInfo.InvariantCulture)?.Trim() ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigureDataSource(
|
||||||
|
IEnumerable<Dictionary<string, object>> rows,
|
||||||
|
IReadOnlyList<ReportColumn> columns)
|
||||||
|
{
|
||||||
|
var normalizedRows = rows
|
||||||
|
.Select(row => NormalizeJsonRow(row, columns))
|
||||||
|
.ToList();
|
||||||
|
if (normalizedRows.Count == 0)
|
||||||
|
{
|
||||||
|
normalizedRows.Add(NormalizeJsonRow(new Dictionary<string, object>(), columns));
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataSource = new JsonDataSource
|
||||||
|
{
|
||||||
|
Name = TreeDataSourceName,
|
||||||
|
JsonSource = new CustomJsonSource(JsonSerializer.Serialize(normalizedRows))
|
||||||
|
};
|
||||||
|
dataSource.Fill();
|
||||||
|
ComponentStorage.Add(dataSource);
|
||||||
|
DataSource = dataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, object> NormalizeJsonRow(
|
||||||
|
IDictionary<string, object> row,
|
||||||
|
IReadOnlyList<ReportColumn> columns)
|
||||||
|
{
|
||||||
|
var normalized = row.ToDictionary(
|
||||||
|
x => x.Key,
|
||||||
|
x => NormalizeJsonValue(x.Value),
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
foreach (var column in columns)
|
||||||
|
{
|
||||||
|
normalized.TryAdd(column.BindingName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized.TryAdd(TreeLevelColumn, 0);
|
||||||
|
normalized.TryAdd(TreeHasChildrenColumn, false);
|
||||||
|
normalized.TryAdd(TreePrefixColumn, string.Empty);
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object NormalizeJsonValue(object value)
|
||||||
|
{
|
||||||
|
if (value == null || value == DBNull.Value)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is Guid guid)
|
||||||
|
{
|
||||||
|
return guid.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is DateTime dateTime)
|
||||||
|
{
|
||||||
|
return dateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is DateTimeOffset dateTimeOffset)
|
||||||
|
{
|
||||||
|
return dateTimeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is byte[] bytes)
|
||||||
|
{
|
||||||
|
return Convert.ToBase64String(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ReportColumn> ApplyImageCounts(
|
||||||
|
IReadOnlyList<ReportColumn> columns,
|
||||||
|
IReadOnlyList<Dictionary<string, object>> rows,
|
||||||
|
string imageBaseUrl)
|
||||||
|
{
|
||||||
|
var imageColumns = columns.Where(x => x.IsImage).ToList();
|
||||||
|
if (imageColumns.Count == 0)
|
||||||
|
{
|
||||||
|
return columns.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var imageCounts = imageColumns.ToDictionary(x => x.BindingName, _ => 1, StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var row in rows)
|
||||||
|
{
|
||||||
|
foreach (var column in imageColumns)
|
||||||
|
{
|
||||||
|
var count = DynamicReportImageHelper.CountImageSources(GetImageRawValue(row, column), imageBaseUrl);
|
||||||
|
if (count > imageCounts[column.BindingName])
|
||||||
|
{
|
||||||
|
imageCounts[column.BindingName] = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns
|
||||||
|
.Select(column => column.IsImage
|
||||||
|
? column with { ImageCount = imageCounts[column.BindingName] }
|
||||||
|
: column)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object GetImageRawValue(IDictionary<string, object> row, ReportColumn column)
|
||||||
|
{
|
||||||
|
if (DynamicReportImageHelper.TryGetRowValue(row, column.BindingName, out var value) &&
|
||||||
|
DynamicReportImageHelper.HasValue(value))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DynamicReportImageHelper.TryGetRowValue(row, column.Field.FieldName, out value) ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildLayout(
|
||||||
|
ListForm listForm,
|
||||||
|
IReadOnlyList<ReportColumn> fields,
|
||||||
|
IStringLocalizer<PlatformResource> localizer,
|
||||||
|
string imageBaseUrl)
|
||||||
|
{
|
||||||
|
Bands.Clear();
|
||||||
|
|
||||||
|
var topMargin = new TopMarginBand { HeightF = Margins.Top };
|
||||||
|
var bottomMargin = new BottomMarginBand { HeightF = 32 };
|
||||||
|
var reportHeader = new ReportHeaderBand { HeightF = 32 };
|
||||||
|
var pageHeader = new PageHeaderBand { HeightF = 28 };
|
||||||
|
var detailHeight = fields.Any(x => x.IsImage)
|
||||||
|
? DynamicReportImageHelper.GetGridImageHeight()
|
||||||
|
: fields.Any(x => x.IsHtml)
|
||||||
|
? 48
|
||||||
|
: 24;
|
||||||
|
var detail = new DetailBand { HeightF = detailHeight };
|
||||||
|
var reportFooter = new ReportFooterBand { HeightF = HasAnySummary(fields) ? 28 : 0 };
|
||||||
|
|
||||||
|
Bands.AddRange([topMargin, reportHeader, pageHeader, detail, reportFooter, bottomMargin]);
|
||||||
|
|
||||||
|
var pageWidth = PageWidth - Margins.Left - Margins.Right;
|
||||||
|
reportHeader.Controls.Add(new XRLabel
|
||||||
|
{
|
||||||
|
Text = DynamicReportImageHelper.GetLocalizedReportTitle(listForm, localizer),
|
||||||
|
BoundsF = new System.Drawing.RectangleF(0, 0, pageWidth, 30),
|
||||||
|
Font = new DXFont("Arial", 14F, DXFontStyle.Bold),
|
||||||
|
TextAlignment = TextAlignment.MiddleLeft
|
||||||
|
});
|
||||||
|
|
||||||
|
bottomMargin.Controls.Add(new XRPageInfo
|
||||||
|
{
|
||||||
|
PageInfo = PageInfo.DateTime,
|
||||||
|
TextFormatString = "{0:dd.MM.yyyy HH:mm}",
|
||||||
|
BoundsF = new System.Drawing.RectangleF(0, 0, pageWidth / 2, 18),
|
||||||
|
Font = new DXFont("Arial", 7.5F),
|
||||||
|
TextAlignment = TextAlignment.MiddleLeft
|
||||||
|
});
|
||||||
|
|
||||||
|
bottomMargin.Controls.Add(new XRPageInfo
|
||||||
|
{
|
||||||
|
TextFormatString = "Sayfa {0} / {1}",
|
||||||
|
BoundsF = new System.Drawing.RectangleF(pageWidth / 2, 0, pageWidth / 2, 18),
|
||||||
|
Font = new DXFont("Arial", 8F),
|
||||||
|
TextAlignment = TextAlignment.MiddleRight
|
||||||
|
});
|
||||||
|
|
||||||
|
var widths = CalculateWidths(fields, pageWidth);
|
||||||
|
var headerTable = CreateTable(pageWidth, 28);
|
||||||
|
var headerRow = new XRTableRow { HeightF = 28 };
|
||||||
|
var detailTable = CreateTable(pageWidth, detailHeight);
|
||||||
|
var detailRow = new XRTableRow { HeightF = detailHeight };
|
||||||
|
var footerTable = CreateTable(pageWidth, 24);
|
||||||
|
var footerRow = new XRTableRow { HeightF = 24 };
|
||||||
|
|
||||||
|
for (var i = 0; i < fields.Count; i++)
|
||||||
|
{
|
||||||
|
var field = fields[i];
|
||||||
|
headerRow.Cells.Add(CreateHeaderCell(field, widths[i], localizer));
|
||||||
|
detailRow.Cells.Add(CreateDetailCell(field, widths[i], imageBaseUrl, detailHeight, i == 0));
|
||||||
|
footerRow.Cells.Add(CreateFooterCell(field, widths[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
headerTable.Rows.Add(headerRow);
|
||||||
|
detailTable.Rows.Add(detailRow);
|
||||||
|
footerTable.Rows.Add(footerRow);
|
||||||
|
|
||||||
|
pageHeader.Controls.Add(headerTable);
|
||||||
|
detail.Controls.Add(detailTable);
|
||||||
|
if (HasAnySummary(fields))
|
||||||
|
{
|
||||||
|
reportFooter.Controls.Add(footerTable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XRTable CreateTable(float width, float height)
|
||||||
|
{
|
||||||
|
return new XRTable
|
||||||
|
{
|
||||||
|
BoundsF = new System.Drawing.RectangleF(0, 0, width, height),
|
||||||
|
Borders = BorderSide.All,
|
||||||
|
BorderColor = TableBorderColor,
|
||||||
|
BorderWidth = 0.25F
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XRTableCell CreateHeaderCell(
|
||||||
|
ReportColumn column,
|
||||||
|
float width,
|
||||||
|
IStringLocalizer<PlatformResource> localizer)
|
||||||
|
{
|
||||||
|
return new XRTableCell
|
||||||
|
{
|
||||||
|
Text = DynamicReportImageHelper.GetLocalizedFieldCaption(column.Field, localizer),
|
||||||
|
WidthF = width,
|
||||||
|
Weight = width,
|
||||||
|
Font = new DXFont("Arial", 8F, DXFontStyle.Bold),
|
||||||
|
BackColor = System.Drawing.Color.FromArgb(238, 242, 247),
|
||||||
|
Padding = new PaddingInfo(4, 4, 2, 2),
|
||||||
|
TextAlignment = TextAlignment.MiddleCenter,
|
||||||
|
Multiline = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XRTableCell CreateDetailCell(
|
||||||
|
ReportColumn column,
|
||||||
|
float width,
|
||||||
|
string imageBaseUrl,
|
||||||
|
float height,
|
||||||
|
bool isTreeColumn)
|
||||||
|
{
|
||||||
|
if (column.IsImage)
|
||||||
|
{
|
||||||
|
return DynamicReportImageHelper.CreateGridImageCell(
|
||||||
|
column.BindingName,
|
||||||
|
column.Field.FieldName,
|
||||||
|
width,
|
||||||
|
imageBaseUrl,
|
||||||
|
column.ImageCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (column.IsHtml)
|
||||||
|
{
|
||||||
|
return CreateHtmlDetailCell(column, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
var cell = new XRTableCell
|
||||||
|
{
|
||||||
|
WidthF = width,
|
||||||
|
Weight = width,
|
||||||
|
Font = new DXFont("Segoe UI Emoji", 8F),
|
||||||
|
Padding = new PaddingInfo(4, 4, 2, 2),
|
||||||
|
TextAlignment = isTreeColumn
|
||||||
|
? TextAlignment.MiddleLeft
|
||||||
|
: DynamicReportImageHelper.ToTextAlignment(column.Field.Alignment, column.Field.SourceDbType),
|
||||||
|
Multiline = false,
|
||||||
|
CanGrow = false
|
||||||
|
};
|
||||||
|
|
||||||
|
var valueExpression = DynamicReportImageHelper.CreateFormatValueExpression($"[{column.BindingName}]", column.Field, column.EditorType);
|
||||||
|
cell.ExpressionBindings.Add(new ExpressionBinding(
|
||||||
|
"BeforePrint",
|
||||||
|
"Text",
|
||||||
|
isTreeColumn
|
||||||
|
? $"Concat([{TreePrefixColumn}], {valueExpression})"
|
||||||
|
: valueExpression));
|
||||||
|
|
||||||
|
if (isTreeColumn)
|
||||||
|
{
|
||||||
|
cell.ExpressionBindings.Add(new ExpressionBinding(
|
||||||
|
"BeforePrint",
|
||||||
|
"Font.Bold",
|
||||||
|
$"[{TreeHasChildrenColumn}]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XRTableCell CreateHtmlDetailCell(ReportColumn column, float width, float height)
|
||||||
|
{
|
||||||
|
var cell = new XRTableCell
|
||||||
|
{
|
||||||
|
WidthF = width,
|
||||||
|
Weight = width,
|
||||||
|
Padding = new PaddingInfo(0, 0, 0, 0),
|
||||||
|
CanGrow = false
|
||||||
|
};
|
||||||
|
|
||||||
|
var richText = new XRRichText
|
||||||
|
{
|
||||||
|
BoundsF = new System.Drawing.RectangleF(0, 0, width, height),
|
||||||
|
Font = new DXFont("Arial", 8F),
|
||||||
|
Padding = new PaddingInfo(4, 4, 2, 2),
|
||||||
|
CanGrow = false
|
||||||
|
};
|
||||||
|
richText.ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Html", $"[{column.BindingName}]"));
|
||||||
|
cell.Controls.Add(richText);
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XRTableCell CreateFooterCell(ReportColumn column, float width)
|
||||||
|
{
|
||||||
|
var summaryType = GetSummaryType(column.Field.TotalSummaryJson);
|
||||||
|
var cell = new XRTableCell
|
||||||
|
{
|
||||||
|
WidthF = width,
|
||||||
|
Weight = width,
|
||||||
|
Font = new DXFont("Arial", 8F, DXFontStyle.Bold),
|
||||||
|
Padding = new PaddingInfo(4, 4, 2, 2),
|
||||||
|
TextAlignment = DynamicReportImageHelper.ToTextAlignment(column.Field.Alignment, column.Field.SourceDbType)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (summaryType == null)
|
||||||
|
{
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.ExpressionBindings.Add(new ExpressionBinding(
|
||||||
|
"BeforePrint",
|
||||||
|
"Text",
|
||||||
|
DynamicReportImageHelper.CreateFormatValueExpression($"sumSum([{column.BindingName}])", column.Field, column.EditorType)));
|
||||||
|
cell.Summary = new XRSummary
|
||||||
|
{
|
||||||
|
Running = SummaryRunning.Report,
|
||||||
|
Func = summaryType.Value,
|
||||||
|
IgnoreNullValues = true
|
||||||
|
};
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float[] CalculateWidths(IReadOnlyList<ReportColumn> fields, float pageWidth)
|
||||||
|
{
|
||||||
|
var raw = fields.Select(x => x.IsImage ? GetImageColumnWidth(x) : GetPreferredColumnWidth(x.Field)).ToArray();
|
||||||
|
var total = raw.Sum();
|
||||||
|
if (total <= 0)
|
||||||
|
{
|
||||||
|
return fields.Select(_ => pageWidth / fields.Count).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw.Select(x => pageWidth * x / total).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetPreferredColumnWidth(ListFormField field)
|
||||||
|
{
|
||||||
|
if (field.Width > 0)
|
||||||
|
{
|
||||||
|
return Math.Max(field.Width.Value, 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
var caption = string.IsNullOrWhiteSpace(field.CaptionName) ? field.FieldName : field.CaptionName;
|
||||||
|
var name = field.FieldName ?? string.Empty;
|
||||||
|
var text = $"{caption} {name}".ToLowerInvariant();
|
||||||
|
|
||||||
|
if (field.SourceDbType == DbType.Boolean)
|
||||||
|
{
|
||||||
|
return 75;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DynamicReportImageHelper.IsNumeric(field.SourceDbType))
|
||||||
|
{
|
||||||
|
return 70;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field.SourceDbType is DbType.Date or DbType.DateTime or DbType.DateTime2 or DbType.DateTimeOffset)
|
||||||
|
{
|
||||||
|
return 115;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.Contains("description") ||
|
||||||
|
text.Contains("explanation") ||
|
||||||
|
text.Contains("note") ||
|
||||||
|
text.Contains("comment") ||
|
||||||
|
text.Contains("address") ||
|
||||||
|
text.Contains("content") ||
|
||||||
|
text.Contains("html") ||
|
||||||
|
text.Contains("text"))
|
||||||
|
{
|
||||||
|
return 220;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.Contains("name") ||
|
||||||
|
text.Contains("title") ||
|
||||||
|
text.Contains("subject"))
|
||||||
|
{
|
||||||
|
return 160;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.Clamp((caption?.Length ?? 0) * 9F + 36F, 85F, 180F);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasAnySummary(IEnumerable<ReportColumn> fields)
|
||||||
|
{
|
||||||
|
return fields.Any(x => GetSummaryType(x.Field.TotalSummaryJson) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetImageColumnWidth(ReportColumn column)
|
||||||
|
{
|
||||||
|
return DynamicReportImageHelper.GetGridImageColumnWidth(column.ImageCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ReportColumn CreateReportColumn(
|
||||||
|
ListFormField field,
|
||||||
|
SelectField selectField,
|
||||||
|
DefaultValueHelper defaultValueHelper,
|
||||||
|
string editorType,
|
||||||
|
bool isImage,
|
||||||
|
bool isHtml)
|
||||||
|
{
|
||||||
|
var bindingName = DynamicReportImageHelper.GetReportFieldName(selectField);
|
||||||
|
var lookup = DynamicReportImageHelper.GetLookup(field);
|
||||||
|
|
||||||
|
if (lookup == null || selectField?.JoinOptions != null)
|
||||||
|
{
|
||||||
|
return new ReportColumn(field, bindingName, null, null, isImage, isHtml, editorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DynamicReportSqlBuilder.IsReportQueryableLookup(lookup))
|
||||||
|
{
|
||||||
|
return new ReportColumn(field, DynamicReportSqlBuilder.GetLookupBindingName(field), lookup, null, isImage, isHtml, editorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lookup.DataSourceType == UiLookupDataSourceTypeEnum.StaticData)
|
||||||
|
{
|
||||||
|
var staticLookupValues = DynamicReportImageHelper.GetStaticLookupValues(lookup, defaultValueHelper);
|
||||||
|
return new ReportColumn(
|
||||||
|
field,
|
||||||
|
staticLookupValues?.Count > 0 ? DynamicReportSqlBuilder.GetLookupBindingName(field) : bindingName,
|
||||||
|
lookup,
|
||||||
|
staticLookupValues,
|
||||||
|
isImage,
|
||||||
|
isHtml,
|
||||||
|
editorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ReportColumn(field, bindingName, null, null, isImage, isHtml, editorType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TreeOptionDto GetTreeOptions(ListForm listForm)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(listForm.TreeOptionJson))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return JsonSerializer.Deserialize<TreeOptionDto>(listForm.TreeOptionJson, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record ReportColumn(
|
||||||
|
ListFormField Field,
|
||||||
|
string BindingName,
|
||||||
|
LookupDto Lookup,
|
||||||
|
Dictionary<string, string> StaticLookupValues,
|
||||||
|
bool IsImage,
|
||||||
|
bool IsHtml,
|
||||||
|
string EditorType,
|
||||||
|
int ImageCount = 1) : DynamicReportSqlBuilder.IReportLookupColumn;
|
||||||
|
|
||||||
|
private static SummaryFunc? GetSummaryType(string totalSummaryJson)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(totalSummaryJson))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = totalSummaryJson.ToLowerInvariant();
|
||||||
|
if (value.Contains("\"summarytype\":\"sum\""))
|
||||||
|
{
|
||||||
|
return SummaryFunc.Sum;
|
||||||
|
}
|
||||||
|
if (value.Contains("\"summarytype\":\"avg\""))
|
||||||
|
{
|
||||||
|
return SummaryFunc.Avg;
|
||||||
|
}
|
||||||
|
if (value.Contains("\"summarytype\":\"min\""))
|
||||||
|
{
|
||||||
|
return SummaryFunc.Min;
|
||||||
|
}
|
||||||
|
if (value.Contains("\"summarytype\":\"max\""))
|
||||||
|
{
|
||||||
|
return SummaryFunc.Max;
|
||||||
|
}
|
||||||
|
if (value.Contains("\"summarytype\":\"count\""))
|
||||||
|
{
|
||||||
|
return SummaryFunc.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetParameter(IDictionary<string, string> parameters, string name) =>
|
||||||
|
DynamicReportImageHelper.GetParameter(parameters, name);
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,6 @@ using System.Linq;
|
||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace Sozsoft.Platform.ReportServices;
|
namespace Sozsoft.Platform.ReportServices;
|
||||||
|
|
||||||
|
|
@ -23,6 +22,7 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
|
||||||
// DynamicReport için ServiceProvider'ı set et
|
// DynamicReport için ServiceProvider'ı set et
|
||||||
Sozsoft.Reports.PredefinedReports.DynamicReport.SetServiceProvider(serviceProvider);
|
Sozsoft.Reports.PredefinedReports.DynamicReport.SetServiceProvider(serviceProvider);
|
||||||
Sozsoft.Reports.PredefinedReports.DynamicGridReport.SetServiceProvider(serviceProvider);
|
Sozsoft.Reports.PredefinedReports.DynamicGridReport.SetServiceProvider(serviceProvider);
|
||||||
|
Sozsoft.Reports.PredefinedReports.DynamicTreeReport.SetServiceProvider(serviceProvider);
|
||||||
Sozsoft.Reports.PredefinedReports.DynamicFormReport.SetServiceProvider(serviceProvider);
|
Sozsoft.Reports.PredefinedReports.DynamicFormReport.SetServiceProvider(serviceProvider);
|
||||||
|
|
||||||
if (!Directory.Exists(reportDirectory))
|
if (!Directory.Exists(reportDirectory))
|
||||||
|
|
@ -38,6 +38,7 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
|
||||||
// DynamicReport için ServiceProvider'ı set et
|
// DynamicReport için ServiceProvider'ı set et
|
||||||
Sozsoft.Reports.PredefinedReports.DynamicReport.SetServiceProvider(serviceProvider);
|
Sozsoft.Reports.PredefinedReports.DynamicReport.SetServiceProvider(serviceProvider);
|
||||||
Sozsoft.Reports.PredefinedReports.DynamicGridReport.SetServiceProvider(serviceProvider);
|
Sozsoft.Reports.PredefinedReports.DynamicGridReport.SetServiceProvider(serviceProvider);
|
||||||
|
Sozsoft.Reports.PredefinedReports.DynamicTreeReport.SetServiceProvider(serviceProvider);
|
||||||
Sozsoft.Reports.PredefinedReports.DynamicFormReport.SetServiceProvider(serviceProvider);
|
Sozsoft.Reports.PredefinedReports.DynamicFormReport.SetServiceProvider(serviceProvider);
|
||||||
|
|
||||||
if (!Directory.Exists(reportDirectory))
|
if (!Directory.Exists(reportDirectory))
|
||||||
|
|
@ -168,6 +169,12 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
|
||||||
return SaveReportLayout(report);
|
return SaveReportLayout(report);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsDynamicTreeReport(reportName))
|
||||||
|
{
|
||||||
|
var report = await Sozsoft.Reports.PredefinedReports.DynamicTreeReport.CreateAsync(parameters);
|
||||||
|
return SaveReportLayout(report);
|
||||||
|
}
|
||||||
|
|
||||||
if (IsDynamicFormReport(reportName))
|
if (IsDynamicFormReport(reportName))
|
||||||
{
|
{
|
||||||
var report = await Sozsoft.Reports.PredefinedReports.DynamicFormReport.CreateAsync(parameters);
|
var report = await Sozsoft.Reports.PredefinedReports.DynamicFormReport.CreateAsync(parameters);
|
||||||
|
|
@ -306,7 +313,7 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsDynamicGridReport(reportName) || IsDynamicFormReport(reportName))
|
if (IsDynamicGridReport(reportName) || IsDynamicTreeReport(reportName) || IsDynamicFormReport(reportName))
|
||||||
{
|
{
|
||||||
if (pathParameters.Length == 1)
|
if (pathParameters.Length == 1)
|
||||||
{
|
{
|
||||||
|
|
@ -328,6 +335,12 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
|
||||||
string.Equals(reportName, Sozsoft.Reports.PredefinedReports.DynamicGridReport.ReportName, StringComparison.OrdinalIgnoreCase);
|
string.Equals(reportName, Sozsoft.Reports.PredefinedReports.DynamicGridReport.ReportName, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsDynamicTreeReport(string reportName)
|
||||||
|
{
|
||||||
|
return string.Equals(reportName, "DynamicTree", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(reportName, Sozsoft.Reports.PredefinedReports.DynamicTreeReport.ReportName, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
private static bool IsDynamicFormReport(string reportName)
|
private static bool IsDynamicFormReport(string reportName)
|
||||||
{
|
{
|
||||||
return string.Equals(reportName, "DynamicForm", StringComparison.OrdinalIgnoreCase) ||
|
return string.Equals(reportName, "DynamicForm", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
|
|
||||||
|
|
@ -186,8 +186,12 @@ const getReportSort = (grid: GridInstance) => {
|
||||||
.join(MULTIVALUE_DELIMITER)
|
.join(MULTIVALUE_DELIMITER)
|
||||||
}
|
}
|
||||||
|
|
||||||
const createDynamicGridReportUrl = (grid: GridInstance | undefined, listFormCode: string) => {
|
const createDynamicListReportUrl = (
|
||||||
const reportUrl = `/admin/reports/DynamicGrid/view/${encodeURIComponent(listFormCode)}`
|
grid: GridInstance | undefined,
|
||||||
|
listFormCode: string,
|
||||||
|
reportName = 'DynamicGrid',
|
||||||
|
) => {
|
||||||
|
const reportUrl = `/admin/reports/${reportName}/view/${encodeURIComponent(listFormCode)}`
|
||||||
if (!grid) {
|
if (!grid) {
|
||||||
return reportUrl
|
return reportUrl
|
||||||
}
|
}
|
||||||
|
|
@ -304,7 +308,9 @@ const useFilters = ({
|
||||||
|
|
||||||
const menus = []
|
const menus = []
|
||||||
const openDynamicGridReport = () => {
|
const openDynamicGridReport = () => {
|
||||||
const reportUrl = createDynamicGridReportUrl(gridRef.current?.instance(), listFormCode)
|
const grid = gridRef.current?.instance()
|
||||||
|
const reportName = grid && isTreeList(grid) ? 'DynamicTree' : 'DynamicGrid'
|
||||||
|
const reportUrl = createDynamicListReportUrl(grid, listFormCode, reportName)
|
||||||
window.open(reportUrl, isPwaMode ? '_self' : '_blank')
|
window.open(reportUrl, isPwaMode ? '_self' : '_blank')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ export type ReportRouteParams = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDynamicGridReport = (report?: string) =>
|
const isDynamicGridReport = (report?: string) =>
|
||||||
report === 'DynamicGrid' || report === 'DynamicGridReport'
|
report === 'DynamicGrid' ||
|
||||||
|
report === 'DynamicGridReport' ||
|
||||||
|
report === 'DynamicTree' ||
|
||||||
|
report === 'DynamicTreeReport'
|
||||||
|
|
||||||
export const normalizeReportRouteParams = ({
|
export const normalizeReportRouteParams = ({
|
||||||
report,
|
report,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue