Dynamic Tree Report hazırlandı

This commit is contained in:
Sedat Öztürk 2026-06-17 22:55:23 +03:00
parent 662edaace6
commit 2118acf6e1
4 changed files with 818 additions and 6 deletions

View file

@ -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);
}

View file

@ -4,7 +4,6 @@ using System.Linq;
using System;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace Sozsoft.Platform.ReportServices;
@ -23,6 +22,7 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
// DynamicReport için ServiceProvider'ı set et
Sozsoft.Reports.PredefinedReports.DynamicReport.SetServiceProvider(serviceProvider);
Sozsoft.Reports.PredefinedReports.DynamicGridReport.SetServiceProvider(serviceProvider);
Sozsoft.Reports.PredefinedReports.DynamicTreeReport.SetServiceProvider(serviceProvider);
Sozsoft.Reports.PredefinedReports.DynamicFormReport.SetServiceProvider(serviceProvider);
if (!Directory.Exists(reportDirectory))
@ -38,6 +38,7 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
// DynamicReport için ServiceProvider'ı set et
Sozsoft.Reports.PredefinedReports.DynamicReport.SetServiceProvider(serviceProvider);
Sozsoft.Reports.PredefinedReports.DynamicGridReport.SetServiceProvider(serviceProvider);
Sozsoft.Reports.PredefinedReports.DynamicTreeReport.SetServiceProvider(serviceProvider);
Sozsoft.Reports.PredefinedReports.DynamicFormReport.SetServiceProvider(serviceProvider);
if (!Directory.Exists(reportDirectory))
@ -168,6 +169,12 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
return SaveReportLayout(report);
}
if (IsDynamicTreeReport(reportName))
{
var report = await Sozsoft.Reports.PredefinedReports.DynamicTreeReport.CreateAsync(parameters);
return SaveReportLayout(report);
}
if (IsDynamicFormReport(reportName))
{
var report = await Sozsoft.Reports.PredefinedReports.DynamicFormReport.CreateAsync(parameters);
@ -306,7 +313,7 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
return;
}
if (IsDynamicGridReport(reportName) || IsDynamicFormReport(reportName))
if (IsDynamicGridReport(reportName) || IsDynamicTreeReport(reportName) || IsDynamicFormReport(reportName))
{
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);
}
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)
{
return string.Equals(reportName, "DynamicForm", StringComparison.OrdinalIgnoreCase) ||

View file

@ -186,8 +186,12 @@ const getReportSort = (grid: GridInstance) => {
.join(MULTIVALUE_DELIMITER)
}
const createDynamicGridReportUrl = (grid: GridInstance | undefined, listFormCode: string) => {
const reportUrl = `/admin/reports/DynamicGrid/view/${encodeURIComponent(listFormCode)}`
const createDynamicListReportUrl = (
grid: GridInstance | undefined,
listFormCode: string,
reportName = 'DynamicGrid',
) => {
const reportUrl = `/admin/reports/${reportName}/view/${encodeURIComponent(listFormCode)}`
if (!grid) {
return reportUrl
}
@ -304,7 +308,9 @@ const useFilters = ({
const menus = []
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')
}

View file

@ -5,7 +5,10 @@ export type ReportRouteParams = {
}
const isDynamicGridReport = (report?: string) =>
report === 'DynamicGrid' || report === 'DynamicGridReport'
report === 'DynamicGrid' ||
report === 'DynamicGridReport' ||
report === 'DynamicTree' ||
report === 'DynamicTreeReport'
export const normalizeReportRouteParams = ({
report,