diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs index 54cac25..3ea5491 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs @@ -4,7 +4,6 @@ using System.Data; using System.Globalization; using System.Linq; using System.Text.Json; -using System.Text.RegularExpressions; using System.Threading.Tasks; using DevExpress.DataAccess.ConnectionParameters; using DevExpress.DataAccess.Sql; @@ -95,7 +94,7 @@ public class DynamicFormReport : XtraReport var queryParams = new QueryParameters { ListFormCode = listFormCode, - Filter = GetParameter(parameters, "filter") ?? CreateKeyFilter(listForm.KeyFieldName, id), + Filter = DynamicReportImageHelper.GetParameter(parameters, "filter") ?? CreateKeyFilter(listForm.KeyFieldName, id), Take = 1, Skip = 0 }; @@ -103,7 +102,9 @@ public class DynamicFormReport : XtraReport selectQueryManager.PrepareQueries(listForm, fields, dataSourceType, customizations, queryParams); var reportFields = GetReportFields(listForm, fields, selectQueryManager, defaultValueHelper); - var selectQuery = StripReportPaging(ApplyQueryParameters(selectQueryManager.SelectQuery, selectQueryManager.SelectQueryParameters)); + var selectQuery = DynamicReportImageHelper.StripReportPaging(DynamicReportImageHelper.ApplyQueryParameters( + selectQueryManager.SelectQuery, + selectQueryManager.SelectQueryParameters)); selectQuery = DynamicReportSqlBuilder.ApplyLookupTextColumns( selectQuery, reportFields, @@ -114,7 +115,7 @@ public class DynamicFormReport : XtraReport ConfigurePage(); ConfigureDataSource(selectQuery, connectionString, dataSourceType); - await BuildLayoutAsync(listForm, reportFields, row, localizer, GetImageBaseUrl(configuration)); + await BuildLayoutAsync(listForm, reportFields, row, localizer, DynamicReportImageHelper.GetImageBaseUrl(configuration)); RequestParameters = false; } @@ -139,7 +140,8 @@ public class DynamicFormReport : XtraReport private void ConfigureDataSource(string selectQuery, string connectionString, DataSourceTypeEnum dataSourceType) { - var dataSource = new SqlDataSource(new CustomStringConnectionParameters(ToDevExpressConnectionString(connectionString, dataSourceType))); + var dataSource = new SqlDataSource(new CustomStringConnectionParameters( + DynamicReportImageHelper.ToDevExpressConnectionString(connectionString, dataSourceType))); dataSource.Queries.Add(new CustomSqlQuery(QueryName, selectQuery)); ComponentStorage.Add(dataSource); DataSource = dataSource; @@ -154,18 +156,20 @@ public class DynamicFormReport : XtraReport string imageBaseUrl) { Bands.Clear(); + var formGroups = GetFormGroups(listForm, fields).ToList(); + var listSubForms = GetListSubForms(listForm, row).ToList(); var topMargin = new TopMarginBand { HeightF = Margins.Top }; var bottomMargin = new BottomMarginBand { HeightF = 32 }; var reportHeader = new ReportHeaderBand { HeightF = 38 }; - var detail = new DetailBand { HeightF = CalculateDetailHeight(fields, listForm, row) }; + var detail = new DetailBand { HeightF = CalculateDetailHeight(formGroups, listSubForms.Count) }; Bands.AddRange([topMargin, reportHeader, detail, bottomMargin]); var pageWidth = PageWidth - Margins.Left - Margins.Right; reportHeader.Controls.Add(new XRLabel { - Text = GetLocalizedReportTitle(listForm, localizer), + Text = DynamicReportImageHelper.GetLocalizedReportTitle(listForm, localizer), BoundsF = new System.Drawing.RectangleF(0, 0, pageWidth, 30), Font = new DXFont("Arial", 14F, DXFontStyle.Bold), TextAlignment = TextAlignment.MiddleLeft @@ -188,13 +192,13 @@ public class DynamicFormReport : XtraReport }); var y = 0F; - foreach (var group in GetFormGroups(listForm, fields)) + foreach (var group in formGroups) { if (!string.IsNullOrWhiteSpace(group.Caption)) { detail.Controls.Add(new XRLabel { - Text = Localize(localizer, group.Caption, group.Caption), + Text = DynamicReportImageHelper.Localize(localizer, group.Caption, group.Caption), BoundsF = new System.Drawing.RectangleF(0, y, pageWidth, 22), Font = new DXFont("Arial", 9F, DXFontStyle.Bold), BackColor = System.Drawing.Color.FromArgb(238, 242, 247), @@ -223,7 +227,7 @@ public class DynamicFormReport : XtraReport y += 8; } - foreach (var subForm in GetListSubForms(listForm, row)) + foreach (var subForm in listSubForms) { detail.Controls.Add(new XRSubreport { @@ -246,7 +250,7 @@ public class DynamicFormReport : XtraReport { return new XRLabel { - Text = GetLocalizedFieldCaption(field.Field, localizer), + Text = DynamicReportImageHelper.GetLocalizedFieldCaption(field.Field, localizer), BoundsF = new System.Drawing.RectangleF(x, y, width, height), Font = new DXFont("Arial", 8F, DXFontStyle.Bold), BackColor = System.Drawing.Color.FromArgb(248, 250, 252), @@ -280,7 +284,7 @@ public class DynamicFormReport : XtraReport BorderColor = FieldBorderColor, BorderWidth = 0.25F, Padding = new PaddingInfo(5, 5, 2, 2), - TextAlignment = ToTextAlignment(field.Field.Alignment, field.Field.SourceDbType), + TextAlignment = DynamicReportImageHelper.ToTextAlignment(field.Field.Alignment, field.Field.SourceDbType), Multiline = true, CanGrow = true }; @@ -392,19 +396,13 @@ public class DynamicFormReport : XtraReport return null; } - if (TryGetRowValue(row, field.BindingName, out var value) && HasValue(value)) + if (DynamicReportImageHelper.TryGetRowValue(row, field.BindingName, out var value) && + DynamicReportImageHelper.HasValue(value)) { return value; } - return TryGetRowValue(row, field.Field.FieldName, out value) ? value : null; - } - - private static bool HasValue(object value) - { - return value != null && - value != DBNull.Value && - !string.IsNullOrWhiteSpace(Convert.ToString(value, CultureInfo.InvariantCulture)); + return DynamicReportImageHelper.TryGetRowValue(row, field.Field.FieldName, out value) ? value : null; } private static bool TryGetFormattedValue( @@ -413,7 +411,7 @@ public class DynamicFormReport : XtraReport out string text) { text = null; - if (row == null || !TryGetRowValue(row, field.BindingName, out var rawValue)) + if (row == null || !DynamicReportImageHelper.TryGetRowValue(row, field.BindingName, out var rawValue)) { return false; } @@ -436,15 +434,14 @@ public class DynamicFormReport : XtraReport } private static float CalculateDetailHeight( - IReadOnlyList fields, - ListForm listForm, - IDictionary row) + IReadOnlyList groups, + int subFormCount) { - var groupHeight = GetFormGroups(listForm, fields) + var groupHeight = groups .Sum(group => (string.IsNullOrWhiteSpace(group.Caption) ? 0 : 26) + group.Fields.Sum(field => GetFieldRowHeight(field) + 3) + 8); - var subFormHeight = GetListSubForms(listForm, row).Count() * 32; + var subFormHeight = subFormCount * 32; return Math.Max(groupHeight + subFormHeight, RowHeight); } @@ -486,11 +483,12 @@ public class DynamicFormReport : XtraReport .Select(x => new FieldWithEditor(x, null)) .ToList(); + var selectFieldsByName = DynamicReportImageHelper.CreateSelectFieldMap(selectQueryManager.SelectFields); return orderedFields - .Where(x => selectQueryManager.SelectFields.Any(s => s.FieldName == x.Field.FieldName)) + .Where(x => selectFieldsByName.ContainsKey(x.Field.FieldName)) .Select(x => { - var selectField = selectQueryManager.SelectFields.First(s => s.FieldName == x.Field.FieldName); + var selectField = selectFieldsByName[x.Field.FieldName]; return CreateFormReportField(x.Field, selectField, x.EditorType, defaultValueHelper); }) .Where(x => !string.IsNullOrWhiteSpace(x.BindingName) && x.BindingName != "undefined") @@ -584,11 +582,12 @@ public class DynamicFormReport : XtraReport var relation = subForm.Relation? .FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.ParentFieldName) && !string.IsNullOrWhiteSpace(x.ChildFieldName) && - TryGetRowValue(row, x.ParentFieldName, out var value) && + DynamicReportImageHelper.TryGetRowValue(row, x.ParentFieldName, out var value) && value != null && value != DBNull.Value); - if (relation == null || !TryGetRowValue(row, relation.ParentFieldName, out var parentValue)) + if (relation == null || + !DynamicReportImageHelper.TryGetRowValue(row, relation.ParentFieldName, out var parentValue)) { continue; } @@ -612,8 +611,8 @@ public class DynamicFormReport : XtraReport string editorType, DefaultValueHelper defaultValueHelper) { - var bindingName = GetReportFieldName(selectField); - var lookup = GetLookup(field); + var bindingName = DynamicReportImageHelper.GetReportFieldName(selectField); + var lookup = DynamicReportImageHelper.GetLookup(field); if (lookup == null || selectField?.JoinOptions != null) { @@ -627,7 +626,7 @@ public class DynamicFormReport : XtraReport if (lookup.DataSourceType == UiLookupDataSourceTypeEnum.StaticData) { - var staticLookupValues = GetStaticLookupValues(lookup, defaultValueHelper); + var staticLookupValues = DynamicReportImageHelper.GetStaticLookupValues(lookup, defaultValueHelper); return new FormReportField( field, staticLookupValues?.Count > 0 ? DynamicReportSqlBuilder.GetLookupBindingName(field) : bindingName, @@ -661,238 +660,6 @@ public class DynamicFormReport : XtraReport return null; } - private static bool TryGetRowValue(IDictionary row, string name, out object value) - { - foreach (var item in row) - { - if (string.Equals(item.Key, name, StringComparison.OrdinalIgnoreCase)) - { - value = item.Value; - return true; - } - } - - value = null; - return false; - } - - private static string ToDevExpressConnectionString(string connectionString, DataSourceTypeEnum dataSourceType) - { - if (string.IsNullOrWhiteSpace(connectionString) || - connectionString.Contains("XpoProvider=", StringComparison.OrdinalIgnoreCase)) - { - return connectionString; - } - - var provider = dataSourceType switch - { - DataSourceTypeEnum.Mssql => "MSSqlServer", - DataSourceTypeEnum.Postgresql => "Postgres", - _ => null - }; - - return string.IsNullOrWhiteSpace(provider) - ? connectionString - : $"XpoProvider={provider};{connectionString}"; - } - - private static string ApplyQueryParameters(string sql, Dictionary parameters) - { - if (string.IsNullOrWhiteSpace(sql) || parameters == null || parameters.Count == 0) - { - return sql; - } - - foreach (var parameter in parameters.OrderByDescending(x => x.Key.Length)) - { - var key = parameter.Key.StartsWith("@") ? parameter.Key : "@" + parameter.Key; - sql = Regex.Replace( - sql, - Regex.Escape(key) + @"\b", - ToSqlLiteral(parameter.Value), - RegexOptions.CultureInvariant); - } - - return sql; - } - - private static string StripReportPaging(string sql) - { - if (string.IsNullOrWhiteSpace(sql)) - { - return sql; - } - - return Regex.Replace( - sql, - @"\s+ORDER\s+BY[\s\S]*?(?:\s+OFFSET\s+\d+\s+ROWS(?:\s+FETCH\s+NEXT\s+\d+\s+ROWS\s+ONLY)?)?\s*$", - string.Empty, - RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - } - - private static string ToSqlLiteral(object value) - { - if (value == null || value == DBNull.Value) - { - return "NULL"; - } - - return value switch - { - string text => $"'{text.Replace("'", "''")}'", - Guid guid => $"'{guid}'", - DateTime date => $"'{date:yyyy-MM-dd HH:mm:ss.fff}'", - DateTimeOffset date => $"'{date:yyyy-MM-dd HH:mm:ss.fff zzz}'", - bool boolean => boolean ? "1" : "0", - byte or sbyte or short or ushort or int or uint or long or ulong or float or double or decimal - => Convert.ToString(value, CultureInfo.InvariantCulture), - _ => $"'{value.ToString()?.Replace("'", "''")}'" - }; - } - - private static string GetReportFieldName(SelectField selectField) - { - if (selectField?.JoinOptions != null && !string.IsNullOrWhiteSpace(selectField.JoinOptions.FieldNameAlias)) - { - return selectField.JoinOptions.FieldNameAlias; - } - - return selectField?.FieldName; - } - - private static LookupDto GetLookup(ListFormField field) - { - if (string.IsNullOrWhiteSpace(field.LookupJson)) - { - return null; - } - - try - { - return JsonSerializer.Deserialize(field.LookupJson); - } - catch (JsonException) - { - return null; - } - } - - private static Dictionary GetStaticLookupValues( - LookupDto lookup, - DefaultValueHelper defaultValueHelper) - { - if (string.IsNullOrWhiteSpace(lookup.LookupQuery)) - { - return null; - } - - try - { - var lookupJson = defaultValueHelper.GetDefaultValue(lookup.LookupQuery); - using var document = JsonDocument.Parse(lookupJson); - if (document.RootElement.ValueKind != JsonValueKind.Array) - { - return null; - } - - var valueExpr = lookup.ValueExpr ?? "Key"; - var displayExpr = lookup.DisplayExpr ?? "Name"; - var values = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var item in document.RootElement.EnumerateArray()) - { - if (!TryGetProperty(item, valueExpr, out var valueElement) || - !TryGetProperty(item, displayExpr, out var displayElement)) - { - continue; - } - - var value = GetJsonScalar(valueElement); - if (value == null) - { - continue; - } - - values[value] = GetJsonScalar(displayElement) ?? value; - } - - return values.Count == 0 ? null : values; - } - catch (JsonException) - { - return null; - } - } - - private static bool TryGetProperty(JsonElement element, string propertyName, out JsonElement value) - { - foreach (var property in element.EnumerateObject()) - { - if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase)) - { - value = property.Value; - return true; - } - } - - value = default; - return false; - } - - private static string GetJsonScalar(JsonElement element) - { - return element.ValueKind switch - { - JsonValueKind.String => element.GetString(), - JsonValueKind.Number => element.GetRawText(), - JsonValueKind.True => bool.TrueString, - JsonValueKind.False => bool.FalseString, - JsonValueKind.Null => null, - _ => element.ToString() - }; - } - - private static string GetLocalizedReportTitle(ListForm listForm, IStringLocalizer localizer) - { - var title = string.IsNullOrWhiteSpace(listForm.Title) ? listForm.Name : listForm.Title; - return Localize(localizer, title, listForm.ListFormCode); - } - - private static string GetLocalizedFieldCaption(ListFormField field, IStringLocalizer localizer) - { - var caption = string.IsNullOrWhiteSpace(field.CaptionName) ? field.FieldName : field.CaptionName; - return Localize(localizer, caption, field.FieldName); - } - - private static string Localize(IStringLocalizer localizer, string key, string fallback) - { - if (string.IsNullOrWhiteSpace(key)) - { - return fallback; - } - - var localized = localizer[key]; - return localized.ResourceNotFound || string.IsNullOrWhiteSpace(localized.Value) - ? key - : localized.Value; - } - - private static TextAlignment ToTextAlignment(string alignment, DbType dbType) - { - return alignment?.ToLowerInvariant() switch - { - "center" => TextAlignment.MiddleCenter, - "right" => TextAlignment.MiddleRight, - "left" => TextAlignment.MiddleLeft, - _ => IsNumeric(dbType) ? TextAlignment.MiddleRight : TextAlignment.MiddleLeft - }; - } - - private static bool IsNumeric(DbType dbType) - { - return dbType is DbType.Currency or DbType.Decimal or DbType.Double or DbType.Int16 or DbType.Int32 - or DbType.Int64 or DbType.Single or DbType.UInt16 or DbType.UInt32 or DbType.UInt64; - } - private static bool IsBooleanEditor(FormReportField field) { return field.EditorType is PlatformConsts.EditorTypes.dxCheckBox or PlatformConsts.EditorTypes.dxSwitch || @@ -928,16 +695,6 @@ public class DynamicFormReport : XtraReport return false; } - private static string GetParameter(IDictionary parameters, string name) - { - return parameters.TryGetValue(name, out var value) ? value : null; - } - - private static string GetImageBaseUrl(IConfiguration configuration) - { - return configuration["App:CdnUrl"] ?? configuration["App:SelfUrl"]; - } - private sealed record FieldWithEditor(ListFormField Field, string EditorType); private sealed record FormGroup(string Caption, IReadOnlyList Fields); private sealed record FormReportField( diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicGridReport.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicGridReport.cs index c10fc68..7d8b130 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicGridReport.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicGridReport.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; using System.Data; -using System.Globalization; using System.Linq; -using System.Text.Json; -using System.Text.RegularExpressions; using System.Threading.Tasks; using DevExpress.DataAccess.ConnectionParameters; using DevExpress.DataAccess.Sql; @@ -100,11 +97,13 @@ public class DynamicGridReport : XtraReport }; 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 => selectQueryManager.SelectFields.Any(s => s.FieldName == x.FieldName)) + .Where(x => selectFieldsByName.ContainsKey(x.FieldName)) .ToList(); if (visibleFields.Count == 0) @@ -112,7 +111,7 @@ public class DynamicGridReport : XtraReport visibleFields = fields .Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined") .Where(x => x.IsActive != false) - .Where(x => selectQueryManager.SelectFields.Any(s => s.FieldName == x.FieldName)) + .Where(x => selectFieldsByName.ContainsKey(x.FieldName)) .Take(8) .ToList(); } @@ -120,24 +119,27 @@ public class DynamicGridReport : XtraReport var reportColumns = visibleFields .Select(field => { - var selectField = selectQueryManager.SelectFields.First(s => s.FieldName == field.FieldName); + var selectField = selectFieldsByName[field.FieldName]; + editorTypesByField.TryGetValue(field.FieldName, out var editorType); return CreateReportColumn( field, selectField, defaultValueHelper, - DynamicReportImageHelper.HasImageEditor(listForm, field.FieldName), - HasHtmlEditor(listForm, field.FieldName)); + DynamicReportImageHelper.IsImageEditor(editorType), + editorType == PlatformConsts.EditorTypes.dxHtmlEditor); }) .Where(x => !string.IsNullOrWhiteSpace(x.BindingName) && x.BindingName != "undefined") .ToList(); - var selectQuery = StripReportPaging(ApplyQueryParameters(selectQueryManager.SelectQuery, selectQueryManager.SelectQueryParameters)); + var selectQuery = DynamicReportImageHelper.StripReportPaging(DynamicReportImageHelper.ApplyQueryParameters( + selectQueryManager.SelectQuery, + selectQueryManager.SelectQueryParameters)); selectQuery = DynamicReportSqlBuilder.ApplyLookupTextColumns( selectQuery, reportColumns, defaultValueHelper, dataSourceType); - var imageBaseUrl = GetImageBaseUrl(configuration); + var imageBaseUrl = DynamicReportImageHelper.GetImageBaseUrl(configuration); reportColumns = await ApplyImageCountsAsync(reportColumns, dynamicDataRepository, selectQuery, connectionString, imageBaseUrl); ConfigurePage(reportColumns); @@ -172,87 +174,14 @@ public class DynamicGridReport : XtraReport private void ConfigureDataSource(string selectQuery, string connectionString, DataSourceTypeEnum dataSourceType) { - var dataSource = new SqlDataSource(new CustomStringConnectionParameters(ToDevExpressConnectionString(connectionString, dataSourceType))); + var dataSource = new SqlDataSource(new CustomStringConnectionParameters( + DynamicReportImageHelper.ToDevExpressConnectionString(connectionString, dataSourceType))); dataSource.Queries.Add(new CustomSqlQuery(QueryName, selectQuery)); ComponentStorage.Add(dataSource); DataSource = dataSource; DataMember = QueryName; } - private static string ToDevExpressConnectionString(string connectionString, DataSourceTypeEnum dataSourceType) - { - if (string.IsNullOrWhiteSpace(connectionString) || - connectionString.Contains("XpoProvider=", StringComparison.OrdinalIgnoreCase)) - { - return connectionString; - } - - var provider = dataSourceType switch - { - DataSourceTypeEnum.Mssql => "MSSqlServer", - DataSourceTypeEnum.Postgresql => "Postgres", - _ => null - }; - - return string.IsNullOrWhiteSpace(provider) - ? connectionString - : $"XpoProvider={provider};{connectionString}"; - } - - private static string ApplyQueryParameters(string sql, Dictionary parameters) - { - if (string.IsNullOrWhiteSpace(sql) || parameters == null || parameters.Count == 0) - { - return sql; - } - - foreach (var parameter in parameters.OrderByDescending(x => x.Key.Length)) - { - var key = parameter.Key.StartsWith("@") ? parameter.Key : "@" + parameter.Key; - sql = Regex.Replace( - sql, - Regex.Escape(key) + @"\b", - ToSqlLiteral(parameter.Value), - RegexOptions.CultureInvariant); - } - - return sql; - } - - private static string StripReportPaging(string sql) - { - if (string.IsNullOrWhiteSpace(sql)) - { - return sql; - } - - return Regex.Replace( - sql, - @"\s+ORDER\s+BY[\s\S]*?(?:\s+OFFSET\s+\d+\s+ROWS(?:\s+FETCH\s+NEXT\s+\d+\s+ROWS\s+ONLY)?)?\s*$", - string.Empty, - RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - } - - private static string ToSqlLiteral(object value) - { - if (value == null || value == DBNull.Value) - { - return "NULL"; - } - - return value switch - { - string text => $"'{text.Replace("'", "''")}'", - Guid guid => $"'{guid}'", - DateTime date => $"'{date:yyyy-MM-dd HH:mm:ss.fff}'", - DateTimeOffset date => $"'{date:yyyy-MM-dd HH:mm:ss.fff zzz}'", - bool boolean => boolean ? "1" : "0", - byte or sbyte or short or ushort or int or uint or long or ulong or float or double or decimal - => Convert.ToString(value, CultureInfo.InvariantCulture), - _ => $"'{value.ToString()?.Replace("'", "''")}'" - }; - } - private static async Task> ApplyImageCountsAsync( IReadOnlyList columns, IDynamicDataRepository dynamicDataRepository, @@ -292,34 +221,13 @@ public class DynamicGridReport : XtraReport private static object GetImageRawValue(IDictionary row, ReportColumn column) { - if (TryGetRowValue(row, column.BindingName, out var value) && HasValue(value)) + if (DynamicReportImageHelper.TryGetRowValue(row, column.BindingName, out var value) && + DynamicReportImageHelper.HasValue(value)) { return value; } - return TryGetRowValue(row, column.Field.FieldName, out value) ? value : null; - } - - private static bool HasValue(object value) - { - return value != null && - value != DBNull.Value && - !string.IsNullOrWhiteSpace(Convert.ToString(value, CultureInfo.InvariantCulture)); - } - - private static bool TryGetRowValue(IDictionary row, string name, out object value) - { - foreach (var item in row) - { - if (string.Equals(item.Key, name, StringComparison.OrdinalIgnoreCase)) - { - value = item.Value; - return true; - } - } - - value = null; - return false; + return DynamicReportImageHelper.TryGetRowValue(row, column.Field.FieldName, out value) ? value : null; } private void BuildLayout( @@ -347,7 +255,7 @@ public class DynamicGridReport : XtraReport var pageWidth = PageWidth - Margins.Left - Margins.Right; reportHeader.Controls.Add(new XRLabel { - Text = GetLocalizedReportTitle(listForm, localizer), + Text = DynamicReportImageHelper.GetLocalizedReportTitle(listForm, localizer), BoundsF = new System.Drawing.RectangleF(0, 0, pageWidth, 30), Font = new DXFont("Arial", 14F, DXFontStyle.Bold), TextAlignment = TextAlignment.MiddleLeft @@ -419,7 +327,7 @@ public class DynamicGridReport : XtraReport var field = column.Field; return new XRTableCell { - Text = GetLocalizedFieldCaption(field, localizer), + Text = DynamicReportImageHelper.GetLocalizedFieldCaption(field, localizer), WidthF = width, Weight = width, Font = new DXFont("Arial", 8F, DXFontStyle.Bold), @@ -430,31 +338,6 @@ public class DynamicGridReport : XtraReport }; } - private static string GetLocalizedReportTitle(ListForm listForm, IStringLocalizer localizer) - { - var title = string.IsNullOrWhiteSpace(listForm.Title) ? listForm.Name : listForm.Title; - return Localize(localizer, title, listForm.ListFormCode); - } - - private static string GetLocalizedFieldCaption(ListFormField field, IStringLocalizer localizer) - { - var caption = string.IsNullOrWhiteSpace(field.CaptionName) ? field.FieldName : field.CaptionName; - return Localize(localizer, caption, field.FieldName); - } - - private static string Localize(IStringLocalizer localizer, string key, string fallback) - { - if (string.IsNullOrWhiteSpace(key)) - { - return fallback; - } - - var localized = localizer[key]; - return localized.ResourceNotFound || string.IsNullOrWhiteSpace(localized.Value) - ? key - : localized.Value; - } - private static XRTableCell CreateDetailCell(ReportColumn column, float width, string imageBaseUrl, float height) { var field = column.Field; @@ -479,7 +362,7 @@ public class DynamicGridReport : XtraReport Weight = width, Font = new DXFont("Segoe UI Emoji", 8F), Padding = new PaddingInfo(4, 4, 2, 2), - TextAlignment = ToTextAlignment(field.Alignment, field.SourceDbType), + TextAlignment = DynamicReportImageHelper.ToTextAlignment(field.Alignment, field.SourceDbType), Multiline = false, CanGrow = false }; @@ -521,7 +404,7 @@ public class DynamicGridReport : XtraReport Weight = width, Font = new DXFont("Arial", 8F, DXFontStyle.Bold), Padding = new PaddingInfo(4, 4, 2, 2), - TextAlignment = ToTextAlignment(field.Alignment, field.SourceDbType) + TextAlignment = DynamicReportImageHelper.ToTextAlignment(field.Alignment, field.SourceDbType) }; if (summaryType == null) @@ -580,7 +463,7 @@ public class DynamicGridReport : XtraReport return 75; } - if (IsNumeric(field.SourceDbType)) + if (DynamicReportImageHelper.IsNumeric(field.SourceDbType)) { return 70; } @@ -622,16 +505,6 @@ public class DynamicGridReport : XtraReport return DynamicReportImageHelper.GetGridImageColumnWidth(column.ImageCount); } - private static string GetReportFieldName(SelectField selectField) - { - if (selectField?.JoinOptions != null && !string.IsNullOrWhiteSpace(selectField.JoinOptions.FieldNameAlias)) - { - return selectField.JoinOptions.FieldNameAlias; - } - - return selectField?.FieldName; - } - private static ReportColumn CreateReportColumn( ListFormField field, SelectField selectField, @@ -639,8 +512,8 @@ public class DynamicGridReport : XtraReport bool isImage, bool isHtml) { - var bindingName = GetReportFieldName(selectField); - var lookup = GetLookup(field); + var bindingName = DynamicReportImageHelper.GetReportFieldName(selectField); + var lookup = DynamicReportImageHelper.GetLookup(field); if (lookup == null || selectField?.JoinOptions != null) { @@ -654,7 +527,7 @@ public class DynamicGridReport : XtraReport if (lookup.DataSourceType == UiLookupDataSourceTypeEnum.StaticData) { - var staticLookupValues = GetStaticLookupValues(lookup, defaultValueHelper); + var staticLookupValues = DynamicReportImageHelper.GetStaticLookupValues(lookup, defaultValueHelper); return new ReportColumn( field, staticLookupValues?.Count > 0 ? DynamicReportSqlBuilder.GetLookupBindingName(field) : bindingName, @@ -667,119 +540,6 @@ public class DynamicGridReport : XtraReport return new ReportColumn(field, bindingName, null, null, isImage, isHtml); } - private static bool HasHtmlEditor(ListForm listForm, string fieldName) - { - if (string.IsNullOrWhiteSpace(listForm?.EditingFormJson) || string.IsNullOrWhiteSpace(fieldName)) - { - return false; - } - - try - { - var groups = JsonSerializer.Deserialize>(listForm.EditingFormJson) ?? []; - return groups - .SelectMany(x => x.Items ?? []) - .Any(x => string.Equals(x.DataField, fieldName, StringComparison.OrdinalIgnoreCase) && - x.EditorType2 == PlatformConsts.EditorTypes.dxHtmlEditor); - } - catch (JsonException) - { - return false; - } - } - - private static LookupDto GetLookup(ListFormField field) - { - if (string.IsNullOrWhiteSpace(field.LookupJson)) - { - return null; - } - - try - { - return JsonSerializer.Deserialize(field.LookupJson); - } - catch (JsonException) - { - return null; - } - } - - private static Dictionary GetStaticLookupValues( - LookupDto lookup, - DefaultValueHelper defaultValueHelper) - { - if (string.IsNullOrWhiteSpace(lookup.LookupQuery)) - { - return null; - } - - try - { - var lookupJson = defaultValueHelper.GetDefaultValue(lookup.LookupQuery); - using var document = JsonDocument.Parse(lookupJson); - if (document.RootElement.ValueKind != JsonValueKind.Array) - { - return null; - } - - var valueExpr = lookup.ValueExpr ?? "Key"; - var displayExpr = lookup.DisplayExpr ?? "Name"; - var values = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var item in document.RootElement.EnumerateArray()) - { - if (!TryGetProperty(item, valueExpr, out var valueElement) || - !TryGetProperty(item, displayExpr, out var displayElement)) - { - continue; - } - - var value = GetJsonScalar(valueElement); - if (value == null) - { - continue; - } - - values[value] = GetJsonScalar(displayElement) ?? value; - } - - return values.Count == 0 ? null : values; - } - catch (JsonException) - { - return null; - } - } - - private static bool TryGetProperty(JsonElement element, string propertyName, out JsonElement value) - { - foreach (var property in element.EnumerateObject()) - { - if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase)) - { - value = property.Value; - return true; - } - } - - value = default; - return false; - } - - private static string GetJsonScalar(JsonElement element) - { - return element.ValueKind switch - { - JsonValueKind.String => element.GetString(), - JsonValueKind.Number => element.GetRawText(), - JsonValueKind.True => bool.TrueString, - JsonValueKind.False => bool.FalseString, - JsonValueKind.Null => null, - _ => element.ToString() - }; - } - - private sealed record ReportColumn( ListFormField Field, string BindingName, @@ -821,30 +581,6 @@ public class DynamicGridReport : XtraReport return null; } - private static TextAlignment ToTextAlignment(string alignment, DbType dbType) - { - return alignment?.ToLowerInvariant() switch - { - "center" => TextAlignment.MiddleCenter, - "right" => TextAlignment.MiddleRight, - "left" => TextAlignment.MiddleLeft, - _ => IsNumeric(dbType) ? TextAlignment.MiddleRight : TextAlignment.MiddleLeft - }; - } - - private static bool IsNumeric(DbType dbType) - { - return dbType is DbType.Currency or DbType.Decimal or DbType.Double or DbType.Int16 or DbType.Int32 - or DbType.Int64 or DbType.Single or DbType.UInt16 or DbType.UInt32 or DbType.UInt64; - } - - private static string GetParameter(IDictionary parameters, string name) - { - return parameters.TryGetValue(name, out var value) ? value : null; - } - - private static string GetImageBaseUrl(IConfiguration configuration) - { - return configuration["App:CdnUrl"] ?? configuration["App:SelfUrl"]; - } + private static string GetParameter(IDictionary parameters, string name) => + DynamicReportImageHelper.GetParameter(parameters, name); } diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportImageHelper.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportImageHelper.cs index 9790d93..7e4191c 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportImageHelper.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportImageHelper.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Data; using System.Globalization; using System.Linq; using System.Net.Http; @@ -11,9 +12,14 @@ using DevExpress.Data.Filtering; using DevExpress.XtraPrinting; using DevExpress.XtraPrinting.Drawing; using DevExpress.XtraReports.UI; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Localization; using Sozsoft.Platform; using Sozsoft.Platform.Entities; +using Sozsoft.Platform.Enums; using Sozsoft.Platform.ListForms; +using Sozsoft.Platform.Localization; +using Sozsoft.Platform.Queries; namespace Sozsoft.Reports.PredefinedReports; @@ -26,6 +32,9 @@ internal static class DynamicReportImageHelper private const int MaxFormImages = 8; private const string ImageUrlFunctionName = "SozReportImageUrl"; private const string ImageSourceFunctionName = "SozReportImageSource"; + private static readonly Regex ReportPagingRegex = new( + @"\s+ORDER\s+BY[\s\S]*?(?:\s+OFFSET\s+\d+\s+ROWS(?:\s+FETCH\s+NEXT\s+\d+\s+ROWS\s+ONLY)?)?\s*$", + RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled); private static readonly HttpClient HttpClient = new(new HttpClientHandler { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator @@ -40,6 +49,289 @@ internal static class DynamicReportImageHelper RegisterImageFunction(new ReportImageSourceFunction()); } + public static Dictionary CreateSelectFieldMap(IEnumerable selectFields) + { + var map = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var selectField in selectFields ?? []) + { + if (!string.IsNullOrWhiteSpace(selectField.FieldName)) + { + map.TryAdd(selectField.FieldName, selectField); + } + } + + return map; + } + + public static Dictionary CreateEditorTypeMap(ListForm listForm) + { + var map = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (string.IsNullOrWhiteSpace(listForm?.EditingFormJson)) + { + return map; + } + + try + { + var groups = JsonSerializer.Deserialize>(listForm.EditingFormJson) ?? []; + foreach (var item in groups.SelectMany(x => x.Items ?? [])) + { + if (!string.IsNullOrWhiteSpace(item.DataField)) + { + map[item.DataField] = item.EditorType2; + } + } + } + catch (JsonException) + { + } + + return map; + } + + public static string GetReportFieldName(SelectField selectField) + { + if (selectField?.JoinOptions != null && !string.IsNullOrWhiteSpace(selectField.JoinOptions.FieldNameAlias)) + { + return selectField.JoinOptions.FieldNameAlias; + } + + return selectField?.FieldName; + } + + public static string ToDevExpressConnectionString(string connectionString, DataSourceTypeEnum dataSourceType) + { + if (string.IsNullOrWhiteSpace(connectionString) || + connectionString.Contains("XpoProvider=", StringComparison.OrdinalIgnoreCase)) + { + return connectionString; + } + + var provider = dataSourceType switch + { + DataSourceTypeEnum.Mssql => "MSSqlServer", + DataSourceTypeEnum.Postgresql => "Postgres", + _ => null + }; + + return string.IsNullOrWhiteSpace(provider) + ? connectionString + : $"XpoProvider={provider};{connectionString}"; + } + + public static string ApplyQueryParameters(string sql, Dictionary parameters) + { + if (string.IsNullOrWhiteSpace(sql) || parameters == null || parameters.Count == 0) + { + return sql; + } + + foreach (var parameter in parameters.OrderByDescending(x => x.Key.Length)) + { + var key = parameter.Key.StartsWith("@") ? parameter.Key : "@" + parameter.Key; + sql = Regex.Replace( + sql, + Regex.Escape(key) + @"\b", + ToSqlLiteral(parameter.Value), + RegexOptions.CultureInvariant); + } + + return sql; + } + + public static string StripReportPaging(string sql) + { + return string.IsNullOrWhiteSpace(sql) + ? sql + : ReportPagingRegex.Replace(sql, string.Empty); + } + + public static string ToSqlLiteral(object value) + { + if (value == null || value == DBNull.Value) + { + return "NULL"; + } + + return value switch + { + string text => $"'{text.Replace("'", "''")}'", + Guid guid => $"'{guid}'", + DateTime date => $"'{date:yyyy-MM-dd HH:mm:ss.fff}'", + DateTimeOffset date => $"'{date:yyyy-MM-dd HH:mm:ss.fff zzz}'", + bool boolean => boolean ? "1" : "0", + byte or sbyte or short or ushort or int or uint or long or ulong or float or double or decimal + => Convert.ToString(value, CultureInfo.InvariantCulture), + _ => $"'{value.ToString()?.Replace("'", "''")}'" + }; + } + + public static bool TryGetRowValue(IDictionary row, string name, out object value) + { + if (row != null && !string.IsNullOrWhiteSpace(name)) + { + if (row.TryGetValue(name, out value)) + { + return true; + } + + foreach (var item in row) + { + if (string.Equals(item.Key, name, StringComparison.OrdinalIgnoreCase)) + { + value = item.Value; + return true; + } + } + } + + value = null; + return false; + } + + public static LookupDto GetLookup(ListFormField field) + { + if (string.IsNullOrWhiteSpace(field.LookupJson)) + { + return null; + } + + try + { + return JsonSerializer.Deserialize(field.LookupJson); + } + catch (JsonException) + { + return null; + } + } + + public static Dictionary GetStaticLookupValues( + LookupDto lookup, + DefaultValueHelper defaultValueHelper) + { + if (string.IsNullOrWhiteSpace(lookup.LookupQuery)) + { + return null; + } + + try + { + var lookupJson = defaultValueHelper.GetDefaultValue(lookup.LookupQuery); + using var document = JsonDocument.Parse(lookupJson); + if (document.RootElement.ValueKind != JsonValueKind.Array) + { + return null; + } + + var valueExpr = lookup.ValueExpr ?? "Key"; + var displayExpr = lookup.DisplayExpr ?? "Name"; + var values = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var item in document.RootElement.EnumerateArray()) + { + if (!TryGetLookupProperty(item, valueExpr, out var valueElement) || + !TryGetLookupProperty(item, displayExpr, out var displayElement)) + { + continue; + } + + var value = GetJsonScalar(valueElement); + if (value == null) + { + continue; + } + + values[value] = GetJsonScalar(displayElement) ?? value; + } + + return values.Count == 0 ? null : values; + } + catch (JsonException) + { + return null; + } + } + + public static string GetLocalizedReportTitle(ListForm listForm, IStringLocalizer localizer) + { + var title = string.IsNullOrWhiteSpace(listForm.Title) ? listForm.Name : listForm.Title; + return Localize(localizer, title, listForm.ListFormCode); + } + + public static string GetLocalizedFieldCaption(ListFormField field, IStringLocalizer localizer) + { + var caption = string.IsNullOrWhiteSpace(field.CaptionName) ? field.FieldName : field.CaptionName; + return Localize(localizer, caption, field.FieldName); + } + + public static string Localize(IStringLocalizer localizer, string key, string fallback) + { + if (string.IsNullOrWhiteSpace(key)) + { + return fallback; + } + + var localized = localizer[key]; + return localized.ResourceNotFound || string.IsNullOrWhiteSpace(localized.Value) + ? key + : localized.Value; + } + + public static TextAlignment ToTextAlignment(string alignment, DbType dbType) + { + return alignment?.ToLowerInvariant() switch + { + "center" => TextAlignment.MiddleCenter, + "right" => TextAlignment.MiddleRight, + "left" => TextAlignment.MiddleLeft, + _ => IsNumeric(dbType) ? TextAlignment.MiddleRight : TextAlignment.MiddleLeft + }; + } + + public static bool IsNumeric(DbType dbType) + { + return dbType is DbType.Currency or DbType.Decimal or DbType.Double or DbType.Int16 or DbType.Int32 + or DbType.Int64 or DbType.Single or DbType.UInt16 or DbType.UInt32 or DbType.UInt64; + } + + public static string GetParameter(IDictionary parameters, string name) + { + return parameters.TryGetValue(name, out var value) ? value : null; + } + + public static string GetImageBaseUrl(IConfiguration configuration) + { + return configuration["App:CdnUrl"] ?? configuration["App:SelfUrl"]; + } + + private static bool TryGetLookupProperty(JsonElement element, string propertyName, out JsonElement value) + { + foreach (var property in element.EnumerateObject()) + { + if (string.Equals(property.Name, propertyName, StringComparison.OrdinalIgnoreCase)) + { + value = property.Value; + return true; + } + } + + value = default; + return false; + } + + private static string GetJsonScalar(JsonElement element) + { + return element.ValueKind switch + { + JsonValueKind.String => element.GetString(), + JsonValueKind.Number => element.GetRawText(), + JsonValueKind.True => bool.TrueString, + JsonValueKind.False => bool.FalseString, + JsonValueKind.Null => null, + _ => element.ToString() + }; + } + public static bool IsImageEditor(string editorType) { return editorType is PlatformConsts.EditorTypes.dxImageUpload or PlatformConsts.EditorTypes.dxImageViewer; @@ -150,11 +442,11 @@ internal static class DynamicReportImageHelper } } - private static bool HasValue(object value) + public static bool HasValue(object value) { return value != null && value != DBNull.Value && - !string.IsNullOrWhiteSpace(Convert.ToString(value)); + !string.IsNullOrWhiteSpace(Convert.ToString(value, CultureInfo.InvariantCulture)); } private static bool HasImageValue(object value) diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportSqlBuilder.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportSqlBuilder.cs index 7602f33..954e28e 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportSqlBuilder.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportSqlBuilder.cs @@ -1,6 +1,4 @@ -using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Text.RegularExpressions; using Sozsoft.Platform; @@ -14,6 +12,12 @@ internal static class DynamicReportSqlBuilder { private const string SourceAlias = "ReportSource"; private const string LookupAlias = "LookupSource"; + private static readonly Regex OrderByRegex = new( + @"(? $"WHEN {ToSqlLiteral(item.Key)} THEN {ToSqlLiteral(item.Value)}"); + .Select(item => $"WHEN {DynamicReportImageHelper.ToSqlLiteral(item.Key)} THEN {DynamicReportImageHelper.ToSqlLiteral(item.Value)}"); return $"CASE {sourceField} {string.Join(" ", cases)} ELSE CAST({sourceField} AS VARCHAR(4000)) END AS {QuoteSqlIdentifier(column.BindingName)}"; } @@ -123,17 +127,9 @@ internal static class DynamicReportSqlBuilder } var query = lookupQuery.Trim().TrimEnd(';'); - query = Regex.Replace( - query, - @"(? $"'{text.Replace("'", "''")}'", - Guid guid => $"'{guid}'", - DateTime date => $"'{date:yyyy-MM-dd HH:mm:ss.fff}'", - DateTimeOffset date => $"'{date:yyyy-MM-dd HH:mm:ss.fff zzz}'", - bool boolean => boolean ? "1" : "0", - byte or sbyte or short or ushort or int or uint or long or ulong or float or double or decimal - => Convert.ToString(value, CultureInfo.InvariantCulture), - _ => $"'{value.ToString()?.Replace("'", "''")}'" - }; - } }