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.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) ||
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue