List Report kısmındaki Gruplar çalıştırıldı

This commit is contained in:
Sedat Öztürk 2026-06-30 22:20:01 +03:00
parent 5970221681
commit 75b459d79e
8 changed files with 405 additions and 66 deletions

View file

@ -399,6 +399,16 @@ public class ListFormSelectAppService : PlatformAppService, IListFormSelectAppSe
parameters.Add("@param" + i, input.Filters[i]);
}
}
var cascadeParameterCount = lookup.CascadeParentFields?
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Length ?? 0;
for (int i = input.Filters?.Length ?? 0; i < cascadeParameterCount; i++)
{
parameters.Add("@param" + i, null!);
}
var lookupQuery = defaultValueHelper.GetDefaultValue(lookup.LookupQuery);
return await dynamicDataRepository.QueryAsync(lookupQuery, connectionString, parameters);
}

View file

@ -129,7 +129,7 @@ public class DynamicFormReport : XtraReport
Margins = CreateReportMargins(HorizontalMargin, HorizontalMargin, 35, 35);
PaperKind = DevExpress.Drawing.Printing.DXPaperKind.A4;
Landscape = false;
Font = new DXFont("Arial", 8.25F);
Font = new DXFont("Arial", 8F);
}
#pragma warning disable CA1416
@ -206,7 +206,7 @@ public class DynamicFormReport : XtraReport
{
Text = DynamicReportImageHelper.Localize(localizer, group.Caption, group.Caption),
BoundsF = new System.Drawing.RectangleF(0, y, pageWidth, 22),
Font = new DXFont("Arial", 9F, DXFontStyle.Bold),
Font = new DXFont("Arial", 8F, DXFontStyle.Bold),
BackColor = System.Drawing.Color.FromArgb(238, 242, 247),
Padding = new PaddingInfo(5, 5, 2, 2),
TextAlignment = TextAlignment.MiddleLeft

View file

@ -29,6 +29,7 @@ public class DynamicGridReport : XtraReport
private const int A4PortraitWidth = 827;
private const int HorizontalMargin = 50;
private const int LandscapeColumnThreshold = 5;
private const float GroupIndentWidth = 18F;
private static readonly System.Drawing.Color TableBorderColor = System.Drawing.Color.FromArgb(226, 232, 240);
private static IServiceScopeFactory _scopeFactory;
@ -122,21 +123,38 @@ public class DynamicGridReport : XtraReport
.Take(8)
.ToList();
}
var groupedFields = DynamicReportGridStateHelper.GetGroupedFields(
availableFields,
gridState?.CustomizationData,
queryParams.Group);
var groupedFieldNames = groupedFields
.Select(x => x.FieldName)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
var reportFields = groupedFieldNames.Count == 0
? visibleFields
: visibleFields
.Where(x => !groupedFieldNames.Contains(x.FieldName))
.ToList();
if (reportFields.Count == 0)
{
reportFields = visibleFields;
}
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")
var groupColumns = CreateReportColumns(
groupedFields,
selectFieldsByName,
editorTypesByField,
defaultValueHelper);
var reportColumns = CreateReportColumns(
reportFields,
selectFieldsByName,
editorTypesByField,
defaultValueHelper);
var allColumns = groupColumns
.Concat(reportColumns)
.GroupBy(x => x.BindingName, StringComparer.OrdinalIgnoreCase)
.Select(x => x.First())
.ToList();
var selectQuery = DynamicReportImageHelper.StripReportPaging(DynamicReportImageHelper.ApplyQueryParameters(
@ -145,7 +163,7 @@ public class DynamicGridReport : XtraReport
dataSourceType));
selectQuery = DynamicReportSqlBuilder.ApplyLookupTextColumns(
selectQuery,
reportColumns,
allColumns,
defaultValueHelper,
dataSourceType);
var imageBaseUrl = DynamicReportImageHelper.GetImageBaseUrl(configuration);
@ -161,10 +179,33 @@ public class DynamicGridReport : XtraReport
selectQuery,
connectionString,
dataSourceType);
BuildLayout(listForm, reportColumns, localizer, imageBaseUrl, currentUser.UserName, criteriaText, isSubReport);
BuildLayout(listForm, reportColumns, groupColumns, localizer, imageBaseUrl, currentUser.UserName, criteriaText, isSubReport);
RequestParameters = false;
}
private static List<ReportColumn> CreateReportColumns(
IEnumerable<ListFormField> fields,
IReadOnlyDictionary<string, SelectField> selectFieldsByName,
IReadOnlyDictionary<string, string> editorTypesByField,
DefaultValueHelper defaultValueHelper)
{
return fields
.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();
}
#pragma warning disable CA1416
private void ConfigurePage(IReadOnlyCollection<ReportColumn> visibleFields, bool isSubReport)
{
@ -174,7 +215,7 @@ public class DynamicGridReport : XtraReport
var requestedContentWidth = visibleFields.Sum(x => x.IsImage ? GetImageColumnWidth(x) : GetPreferredColumnWidth(x.Field));
Landscape = !isSubReport &&
(visibleFields.Count >= LandscapeColumnThreshold || requestedContentWidth > portraitContentWidth);
Font = new DXFont("Arial", 8.25F);
Font = new DXFont("Arial", 8F);
}
private static System.Drawing.Printing.Margins CreateReportMargins(
@ -248,6 +289,7 @@ public class DynamicGridReport : XtraReport
private void BuildLayout(
ListForm listForm,
IReadOnlyList<ReportColumn> fields,
IReadOnlyList<ReportColumn> groupFields,
IStringLocalizer<PlatformResource> localizer,
string imageBaseUrl,
string currentUserName,
@ -256,6 +298,7 @@ public class DynamicGridReport : XtraReport
{
Bands.Clear();
var pageWidth = PageWidth - Margins.Left - Margins.Right;
var topMargin = new TopMarginBand { HeightF = Margins.Top };
var bottomMargin = new BottomMarginBand { HeightF = 32 };
var reportHeader = new ReportHeaderBand
@ -270,10 +313,15 @@ public class DynamicGridReport : XtraReport
: 24;
var detail = new DetailBand { HeightF = detailHeight };
var reportFooter = new ReportFooterBand { HeightF = HasAnySummary(fields) ? 28 : 0 };
var groupHeaderBands = CreateGroupHeaderBands(groupFields, localizer, pageWidth);
Bands.AddRange([topMargin, reportHeader, pageHeader, detail, reportFooter, bottomMargin]);
Bands.AddRange([topMargin, reportHeader, pageHeader]);
foreach (var groupHeaderBand in groupHeaderBands.AsEnumerable().Reverse())
{
Bands.Add(groupHeaderBand);
}
Bands.AddRange([detail, reportFooter, bottomMargin]);
var pageWidth = PageWidth - Margins.Left - Margins.Right;
reportHeader.Controls.Add(new XRLabel
{
Text = DynamicReportImageHelper.GetLocalizedReportTitle(listForm, localizer),
@ -302,21 +350,24 @@ public class DynamicGridReport : XtraReport
};
bottomMargin.Controls.Add(pageInfo);
var widths = CalculateWidths(fields, pageWidth);
var detailIndent = GetGroupDetailIndent(groupFields.Count, pageWidth);
var tableWidth = pageWidth - detailIndent;
var detailWidths = CalculateWidths(fields, tableWidth);
var headerWidths = CreateIndentedHeaderWidths(detailWidths, detailIndent);
var headerTable = CreateTable(pageWidth, 28);
var headerRow = new XRTableRow { HeightF = 28 };
var detailTable = CreateTable(pageWidth, detailHeight);
var detailTable = CreateTable(tableWidth, detailHeight, detailIndent);
detailTable.CanGrow = true;
var detailRow = new XRTableRow { HeightF = detailHeight, CanGrow = true };
var footerTable = CreateTable(pageWidth, 24);
var footerTable = CreateTable(tableWidth, 24, detailIndent);
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));
footerRow.Cells.Add(CreateFooterCell(field, widths[i]));
headerRow.Cells.Add(CreateHeaderCell(field, headerWidths[i], localizer));
detailRow.Cells.Add(CreateDetailCell(field, detailWidths[i], imageBaseUrl, detailHeight));
footerRow.Cells.Add(CreateFooterCell(field, detailWidths[i]));
}
headerTable.Rows.Add(headerRow);
@ -331,11 +382,99 @@ public class DynamicGridReport : XtraReport
}
}
private static XRTable CreateTable(float width, float height)
private static List<GroupHeaderBand> CreateGroupHeaderBands(
IReadOnlyList<ReportColumn> groupFields,
IStringLocalizer<PlatformResource> localizer,
float pageWidth)
{
var bands = new List<GroupHeaderBand>();
for (var i = 0; i < groupFields.Count; i++)
{
var groupField = groupFields[i];
var level = groupFields.Count - i - 1;
var band = new GroupHeaderBand
{
HeightF = 26,
KeepTogether = true,
RepeatEveryPage = true,
Level = level,
BackColor = System.Drawing.Color.FromArgb(248, 250, 252)
};
band.GroupFields.Add(new GroupField(groupField.BindingName, XRColumnSortOrder.Ascending));
var indent = GetGroupHeaderIndent(i, pageWidth);
var caption = DynamicReportImageHelper.GetLocalizedFieldCaption(groupField.Field, localizer);
var availableWidth = pageWidth - indent;
var captionWidth = Math.Clamp(caption.Length * 6F + 28F, 90F, Math.Min(220F, availableWidth / 2));
var captionLabel = new XRLabel
{
Text = $"{caption}:",
BoundsF = new System.Drawing.RectangleF(indent, 3, captionWidth, 20),
Font = new DXFont("Arial", 8F, DXFontStyle.Bold),
Padding = new PaddingInfo(6, 2, 0, 0),
TextAlignment = TextAlignment.MiddleLeft,
BackColor = System.Drawing.Color.FromArgb(226, 232, 240),
Borders = BorderSide.Left | BorderSide.Top | BorderSide.Bottom,
BorderColor = TableBorderColor,
BorderWidth = 0.25F
};
var valueLabel = new XRLabel
{
BoundsF = new System.Drawing.RectangleF(indent + captionWidth, 3, availableWidth - captionWidth, 20),
Font = new DXFont("Arial", 8F, DXFontStyle.Bold),
Padding = new PaddingInfo(6, 6, 0, 0),
TextAlignment = TextAlignment.MiddleLeft,
BackColor = System.Drawing.Color.FromArgb(241, 245, 249),
Borders = BorderSide.All,
BorderColor = TableBorderColor,
BorderWidth = 0.25F,
CanGrow = true,
Multiline = true
};
valueLabel.ExpressionBindings.Add(new ExpressionBinding(
"BeforePrint",
"Text",
DynamicReportImageHelper.CreateFormatValueExpression(
$"[{groupField.BindingName}]",
groupField.Field,
groupField.EditorType)));
band.Controls.AddRange([captionLabel, valueLabel]);
bands.Add(band);
}
return bands;
}
private static float GetGroupHeaderIndent(int groupIndex, float pageWidth)
{
return Math.Min(groupIndex * GroupIndentWidth, pageWidth * 0.25F);
}
private static float GetGroupDetailIndent(int groupCount, float pageWidth)
{
return Math.Min(groupCount * GroupIndentWidth, pageWidth * 0.25F);
}
private static float[] CreateIndentedHeaderWidths(float[] detailWidths, float detailIndent)
{
if (detailWidths.Length == 0 || detailIndent <= 0)
{
return detailWidths;
}
var headerWidths = detailWidths.ToArray();
headerWidths[0] += detailIndent;
return headerWidths;
}
private static XRTable CreateTable(float width, float height, float left = 0)
{
return new XRTable
{
BoundsF = new System.Drawing.RectangleF(0, 0, width, height),
BoundsF = new System.Drawing.RectangleF(left, 0, width, height),
Borders = BorderSide.All,
BorderColor = TableBorderColor,
BorderWidth = 0.25F
@ -387,7 +526,7 @@ public class DynamicGridReport : XtraReport
{
WidthF = width,
Weight = width,
Font = new DXFont("Segoe UI Emoji", 8F),
Font = new DXFont("Arial", 8F),
Padding = new PaddingInfo(4, 4, 2, 2),
TextAlignment = DynamicReportImageHelper.ToTextAlignment(field.Alignment, field.SourceDbType),
Multiline = true,
@ -475,18 +614,6 @@ public class DynamicGridReport : XtraReport
};
}
private static float[] CalculateWidths(IReadOnlyList<ListFormField> fields, float pageWidth)
{
var raw = fields.Select(x => (float)Math.Max(x.Width ?? 90, 60)).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[] CalculateWidths(IReadOnlyList<ReportColumn> fields, float pageWidth)
{
var raw = fields.Select(x => x.IsImage ? GetImageColumnWidth(x) : GetPreferredColumnWidth(x.Field)).ToArray();

View file

@ -55,7 +55,7 @@ public partial class DynamicReport : XtraReport
HorizontalMargin,
VerticalMargin,
VerticalMargin);
Font = new DXFont("Arial", 8.25F);
Font = new DXFont("Arial", 8F);
TopMargin.HeightF = Margins.Top;
BottomMargin.HeightF = Margins.Bottom;

View file

@ -2,7 +2,9 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Sozsoft.Platform;
using Sozsoft.Platform.Entities;
using Sozsoft.Platform.ListForms;
namespace Sozsoft.Reports.PredefinedReports;
@ -32,6 +34,51 @@ internal static class DynamicReportGridStateHelper
.ToList();
}
public static List<ListFormField> GetGroupedFields(
IReadOnlyList<ListFormField> availableFields,
string gridStateJson,
string groupParameter)
{
var groupedFields = new List<ListFormField>();
var groupedFieldNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var field in GetGroupFieldsFromParameter(availableFields, groupParameter))
{
AddGroupedField(field);
}
if (TryGetColumnStates(gridStateJson, out var statesByField))
{
foreach (var field in availableFields.Where(field =>
statesByField.TryGetValue(field.FieldName, out var state) &&
state.GroupIndex.HasValue)
.OrderBy(field => statesByField[field.FieldName].GroupIndex)
.ThenBy(field => statesByField[field.FieldName].VisibleIndex))
{
AddGroupedField(field);
}
}
foreach (var field in availableFields
.Select(field => new { Field = field, GroupIndex = GetConfiguredGroupIndex(field) })
.Where(x => x.GroupIndex.HasValue)
.OrderBy(x => x.GroupIndex)
.Select(x => x.Field))
{
AddGroupedField(field);
}
return groupedFields;
void AddGroupedField(ListFormField field)
{
if (!string.IsNullOrWhiteSpace(field?.FieldName) && groupedFieldNames.Add(field.FieldName))
{
groupedFields.Add(field);
}
}
}
private static string GetStateStorageKey(ListForm listForm)
{
if (!string.IsNullOrWhiteSpace(listForm.StateStoringJson))
@ -100,10 +147,14 @@ internal static class DynamicReportGridStateHelper
visibleIndexProperty.TryGetInt32(out var value)
? value
: states.Count;
var groupIndex = TryGetProperty(column, "groupIndex", out var groupIndexProperty) &&
groupIndexProperty.TryGetInt32(out var groupValue)
? groupValue
: (int?)null;
if (!string.IsNullOrWhiteSpace(fieldName))
{
states.Add(new GridColumnState(fieldName, visible, visibleIndex));
states.Add(new GridColumnState(fieldName, visible, visibleIndex, groupIndex));
}
if (TryGetProperty(column, "columns", out var childColumns) &&
@ -131,6 +182,55 @@ internal static class DynamicReportGridStateHelper
return null;
}
private static List<ListFormField> GetGroupFieldsFromParameter(
IReadOnlyList<ListFormField> availableFields,
string groupParameter)
{
var result = new List<ListFormField>();
if (string.IsNullOrWhiteSpace(groupParameter))
{
return result;
}
var fieldsByName = availableFields
.Where(x => !string.IsNullOrWhiteSpace(x.FieldName))
.ToDictionary(x => x.FieldName, StringComparer.OrdinalIgnoreCase);
foreach (var group in groupParameter.Split(
PlatformConsts.MultiValueDelimiter,
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
var parts = group.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (parts.Length > 0 && fieldsByName.TryGetValue(parts[0], out var field))
{
result.Add(field);
}
}
return result;
}
private static int? GetConfiguredGroupIndex(ListFormField field)
{
if (string.IsNullOrWhiteSpace(field.GroupingJson))
{
return null;
}
try
{
var grouping = JsonSerializer.Deserialize<ColumnGroupingDto>(field.GroupingJson, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
return grouping?.GroupIndex;
}
catch (JsonException)
{
return null;
}
}
private static bool TryGetProperty(JsonElement element, string name, out JsonElement value)
{
if (element.ValueKind == JsonValueKind.Object)
@ -149,5 +249,5 @@ internal static class DynamicReportGridStateHelper
return false;
}
private sealed record GridColumnState(string FieldName, bool Visible, int VisibleIndex);
private sealed record GridColumnState(string FieldName, bool Visible, int VisibleIndex, int? GroupIndex);
}

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
@ -52,7 +53,7 @@ internal static class DynamicReportSqlBuilder
var lookupSelectParts = columns
.Select(column => CreateLookupSelectPart(column, defaultValueHelper, dataSourceType))
.Where(part => !string.IsNullOrWhiteSpace(part))
.Where(part => part != null && !string.IsNullOrWhiteSpace(part.SelectSql))
.ToList();
if (lookupSelectParts.Count == 0)
@ -68,10 +69,19 @@ internal static class DynamicReportSqlBuilder
normalizedSql = OrderByRegex.Replace(normalizedSql, string.Empty).Trim();
}
return $"SELECT {sourceAlias}.*, {string.Join(", ", lookupSelectParts)} FROM ({normalizedSql}) AS {sourceAlias}{orderBy}";
var commonTableExpressions = lookupSelectParts
.Select(part => part.CommonTableExpression)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct()
.ToList();
var withClause = commonTableExpressions.Count > 0
? $"WITH {string.Join(", ", commonTableExpressions)} "
: string.Empty;
return $"{withClause}SELECT {sourceAlias}.*, {string.Join(", ", lookupSelectParts.Select(x => x.SelectSql))} FROM ({normalizedSql}) AS {sourceAlias}{orderBy}";
}
private static string CreateLookupSelectPart(
private static LookupSelectPart CreateLookupSelectPart(
IReportLookupColumn column,
DefaultValueHelper defaultValueHelper,
DataSourceTypeEnum dataSourceType)
@ -89,7 +99,7 @@ internal static class DynamicReportSqlBuilder
return null;
}
private static string CreateStaticLookupSelectPart(
private static LookupSelectPart CreateStaticLookupSelectPart(
IReportLookupColumn column,
DataSourceTypeEnum dataSourceType)
{
@ -102,10 +112,11 @@ internal static class DynamicReportSqlBuilder
var cases = column.StaticLookupValues
.Select(item => $"WHEN {DynamicReportImageHelper.ToSqlLiteral(item.Key, dataSourceType)} THEN {DynamicReportImageHelper.ToSqlLiteral(item.Value, dataSourceType)}");
return $"CASE {sourceField} {string.Join(" ", cases)} ELSE CAST({sourceField} AS VARCHAR(4000)) END AS {QuoteSqlIdentifier(column.BindingName)}";
return new LookupSelectPart(
$"CASE {sourceField} {string.Join(" ", cases)} ELSE CAST({sourceField} AS VARCHAR(4000)) END AS {QuoteSqlIdentifier(column.BindingName)}");
}
private static string CreateQueryLookupSelectPart(
private static LookupSelectPart CreateQueryLookupSelectPart(
IReportLookupColumn column,
DefaultValueHelper defaultValueHelper,
DataSourceTypeEnum dataSourceType)
@ -121,6 +132,7 @@ internal static class DynamicReportSqlBuilder
return CreateQueryMultiLookupSelectPart(column, lookupQuery, dataSourceType);
}
var (commonTableExpression, lookupSelectQuery) = SplitCommonTableExpression(lookupQuery);
var lookupAlias = QuoteSqlIdentifier(LookupAlias);
var valueExpr = QuoteSqlIdentifier(column.Lookup.ValueExpr ?? "Key");
var displayExpr = QuoteSqlIdentifier(column.Lookup.DisplayExpr ?? "Name");
@ -128,13 +140,13 @@ internal static class DynamicReportSqlBuilder
var resultAlias = QuoteSqlIdentifier(column.BindingName);
var scalarQuery = dataSourceType == DataSourceTypeEnum.Mssql
? $"SELECT TOP 1 {lookupAlias}.{displayExpr} FROM ({lookupQuery}) AS {lookupAlias} WHERE {lookupAlias}.{valueExpr} = {sourceField}"
: $"SELECT {lookupAlias}.{displayExpr} FROM ({lookupQuery}) AS {lookupAlias} WHERE {lookupAlias}.{valueExpr} = {sourceField} LIMIT 1";
? $"SELECT TOP 1 {lookupAlias}.{displayExpr} FROM ({lookupSelectQuery}) AS {lookupAlias} WHERE {lookupAlias}.{valueExpr} = {sourceField}"
: $"SELECT {lookupAlias}.{displayExpr} FROM ({lookupSelectQuery}) AS {lookupAlias} WHERE {lookupAlias}.{valueExpr} = {sourceField} LIMIT 1";
return $"({scalarQuery}) AS {resultAlias}";
return new LookupSelectPart($"({scalarQuery}) AS {resultAlias}", commonTableExpression);
}
private static string CreateStaticMultiLookupSelectPart(
private static LookupSelectPart CreateStaticMultiLookupSelectPart(
IReportLookupColumn column,
DataSourceTypeEnum dataSourceType)
{
@ -147,27 +159,28 @@ internal static class DynamicReportSqlBuilder
if (dataSourceType == DataSourceTypeEnum.Mssql)
{
return $"""
return new LookupSelectPart($"""
(SELECT STRING_AGG(CAST({LookupAliasForValues()}.DisplayValue AS NVARCHAR(MAX)), N', ')
FROM (VALUES {values}) AS {LookupAliasForValues()}(ValueKey, DisplayValue)
INNER JOIN STRING_SPLIT(CAST({sourceField} AS NVARCHAR(MAX)), N'{PlatformConsts.MultiValueDelimiter}') AS SplitValues
ON CAST({LookupAliasForValues()}.ValueKey AS NVARCHAR(4000)) = LTRIM(RTRIM(SplitValues.value))) AS {resultAlias}
""".ReplaceLineEndings(" ").Trim();
""".ReplaceLineEndings(" ").Trim());
}
return $"""
return new LookupSelectPart($"""
(SELECT string_agg(CAST({LookupAliasForValues()}."DisplayValue" AS TEXT), ', ')
FROM (VALUES {values}) AS {LookupAliasForValues()}("ValueKey", "DisplayValue")
INNER JOIN unnest(string_to_array(CAST({sourceField} AS TEXT), '{PlatformConsts.MultiValueDelimiter}')) AS SplitValues(value)
ON CAST({LookupAliasForValues()}."ValueKey" AS TEXT) = btrim(SplitValues.value)) AS {resultAlias}
""".ReplaceLineEndings(" ").Trim();
""".ReplaceLineEndings(" ").Trim());
}
private static string CreateQueryMultiLookupSelectPart(
private static LookupSelectPart CreateQueryMultiLookupSelectPart(
IReportLookupColumn column,
string lookupQuery,
DataSourceTypeEnum dataSourceType)
{
var (commonTableExpression, lookupSelectQuery) = SplitCommonTableExpression(lookupQuery);
var lookupAlias = QuoteSqlIdentifier(LookupAlias);
var valueExpr = QuoteSqlIdentifier(column.Lookup.ValueExpr ?? "Key");
var displayExpr = QuoteSqlIdentifier(column.Lookup.DisplayExpr ?? "Name");
@ -177,18 +190,92 @@ internal static class DynamicReportSqlBuilder
var scalarQuery = dataSourceType == DataSourceTypeEnum.Mssql
? $"""
SELECT STRING_AGG(CAST({lookupAlias}.{displayExpr} AS NVARCHAR(MAX)), N', ')
FROM ({lookupQuery}) AS {lookupAlias}
FROM ({lookupSelectQuery}) AS {lookupAlias}
INNER JOIN STRING_SPLIT(CAST({sourceField} AS NVARCHAR(MAX)), N'{PlatformConsts.MultiValueDelimiter}') AS SplitValues
ON CAST({lookupAlias}.{valueExpr} AS NVARCHAR(4000)) = LTRIM(RTRIM(SplitValues.value))
""".ReplaceLineEndings(" ").Trim()
: $"""
SELECT string_agg(CAST({lookupAlias}.{displayExpr} AS TEXT), ', ')
FROM ({lookupQuery}) AS {lookupAlias}
FROM ({lookupSelectQuery}) AS {lookupAlias}
INNER JOIN unnest(string_to_array(CAST({sourceField} AS TEXT), '{PlatformConsts.MultiValueDelimiter}')) AS SplitValues(value)
ON CAST({lookupAlias}.{valueExpr} AS TEXT) = btrim(SplitValues.value)
""".ReplaceLineEndings(" ").Trim();
return $"({scalarQuery}) AS {resultAlias}";
return new LookupSelectPart($"({scalarQuery}) AS {resultAlias}", commonTableExpression);
}
private static (string CommonTableExpression, string SelectQuery) SplitCommonTableExpression(string query)
{
var normalized = query.Trim().TrimStart(';').TrimStart();
if (!normalized.StartsWith("WITH ", System.StringComparison.OrdinalIgnoreCase) &&
!normalized.StartsWith("WITH\t", System.StringComparison.OrdinalIgnoreCase) &&
!normalized.StartsWith("WITH\r", System.StringComparison.OrdinalIgnoreCase) &&
!normalized.StartsWith("WITH\n", System.StringComparison.OrdinalIgnoreCase))
{
return (null, normalized);
}
var depth = 0;
var quote = '\0';
for (var index = 4; index < normalized.Length; index++)
{
var current = normalized[index];
if (quote != '\0')
{
if (current == quote)
{
if (index + 1 < normalized.Length && normalized[index + 1] == quote)
{
index++;
}
else
{
quote = '\0';
}
}
continue;
}
if (current is '\'' or '"')
{
quote = current;
continue;
}
if (current == '(')
{
depth++;
continue;
}
if (current == ')')
{
depth--;
continue;
}
if (depth == 0 && IsKeywordAt(normalized, index, "SELECT"))
{
return (normalized[4..index].Trim(), normalized[index..].Trim());
}
}
return (null, normalized);
}
private static bool IsKeywordAt(string value, int index, string keyword)
{
if (index + keyword.Length > value.Length ||
!value.AsSpan(index, keyword.Length).Equals(keyword, System.StringComparison.OrdinalIgnoreCase))
{
return false;
}
var hasBoundaryBefore = index == 0 || !char.IsLetterOrDigit(value[index - 1]) && value[index - 1] != '_';
var after = index + keyword.Length;
var hasBoundaryAfter = after == value.Length || !char.IsLetterOrDigit(value[after]) && value[after] != '_';
return hasBoundaryBefore && hasBoundaryAfter;
}
private static string LookupAliasForValues() => "LookupValues";
@ -227,4 +314,6 @@ internal static class DynamicReportSqlBuilder
return $"\"{identifier.Replace("\"", "\"\"")}\"";
}
private sealed record LookupSelectPart(string SelectSql, string CommonTableExpression = null);
}

View file

@ -183,7 +183,7 @@ public class DynamicTreeReport : XtraReport
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);
Font = new DXFont("Arial", 8F);
}
private static System.Drawing.Printing.Margins CreateReportMargins(
@ -550,7 +550,7 @@ public class DynamicTreeReport : XtraReport
{
WidthF = width,
Weight = width,
Font = new DXFont("Segoe UI Emoji", 8F),
Font = new DXFont("Arial", 8F),
Padding = new PaddingInfo(4, 4, 2, 2),
TextAlignment = isTreeColumn
? TextAlignment.MiddleLeft

View file

@ -1,6 +1,19 @@
{
"commit": "771ea95",
"commit": "e7670c6",
"releases": [
{
"version": "1.1.05",
"buildDate": "2026-06-27",
"commit": "352cf047b0af1b8aad53b632a41161efcc6cecdf",
"changeLog": [
"- Ai için Ollama kurulumu yapıldı. Ayrıca Gemma LLM ile çalıştırıldı.",
"- DynamicReport eklendi. ReportDesigner ile çalışan bir rapor tasarlandı.",
"- DynamicGridReport eklendi. Grid üzerinde otomatik rapor alınabiliyor.",
"- DynamicTreeReport eklendi. Tree üzerinden otomatik rapor alınabiliyor.",
"- DynamicFormReport eklendi. Single ekranlarda otomatik rapor eklendi.",
"- Demo sayfası menüden ayrıldı."
]
},
{
"version": "1.1.04",
"buildDate": "2026-06-04",