From fdf48c04ee31fe055a74caa9aa9cbb911b37d47d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Tue, 16 Jun 2026 17:46:20 +0300 Subject: [PATCH] =?UTF-8?q?DynamicForm,=20DynamicList=20ve=20DynamicReport?= =?UTF-8?q?=20g=C3=BCncellemesi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Seeds/ListFormSeeder_Administration.cs | 19 +- .../Seeds/ListFormSeeder_Saas.cs | 2 +- .../Seeds/MenusData.json | 4 +- .../Seeds/TenantData.json | 20 +- .../PredefinedReports/DynamicFormReport.cs | 849 ++++++++++++++++++ ...ListFormReport.cs => DynamicGridReport.cs} | 12 +- ....Designer.cs => DynamicReport.Designer.cs} | 4 +- .../{TemplateReport.cs => DynamicReport.cs} | 4 +- ...TemplateReport.resx => DynamicReport.resx} | 0 .../PredefinedReports/TestReport.Designer.cs | 264 ------ .../PredefinedReports/TestReport.cs | 9 - .../PredefinedReports/TestReport.resx | 123 --- .../CustomReportStorageWebExtension.cs | 49 +- .../views/report/DevexpressReportViewer.tsx | 24 +- 14 files changed, 947 insertions(+), 436 deletions(-) create mode 100644 api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs rename api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/{DynamicListFormReport.cs => DynamicGridReport.cs} (98%) rename api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/{TemplateReport.Designer.cs => DynamicReport.Designer.cs} (99%) rename api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/{TemplateReport.cs => DynamicReport.cs} (96%) rename api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/{TemplateReport.resx => DynamicReport.resx} (100%) delete mode 100644 api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TestReport.Designer.cs delete mode 100644 api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TestReport.cs delete mode 100644 api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TestReport.resx diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs index e39e2f1..fe694a5 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Administration.cs @@ -1584,28 +1584,37 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep Text ="App.Platform.View", UrlTarget="_blank", AuthName = listFormName, - Url=$"/admin/reports/TemplateReport/view?id=@Id", + Url=$"/admin/reports/DynamicReport/view?id=@Id", IsVisible = true, - VisibleExpression = "((e)=> e.row.data.CategoryId !== 'Listeler')" + VisibleExpression = "((e)=> e.row.data.CategoryId === 'Raporlar')" }, new() { Hint = "App.Platform.Design", Text ="App.Platform.Design", UrlTarget="_blank", AuthName = listFormName + ".Update", - Url=$"/admin/reports/TemplateReport/design?id=@Id", + Url=$"/admin/reports/DynamicReport/design?id=@Id", IsVisible = true, - VisibleExpression = "((e)=> e.row.data.CategoryId !== 'Listeler')" + VisibleExpression = "((e)=> e.row.data.CategoryId === 'Raporlar')" }, new() { Hint = "App.Platform.View", Text ="App.Platform.View", UrlTarget="_blank", AuthName = listFormName, - Url=$"/admin/reports/DynamicListFormReport/view?listFormCode=@Name", + Url=$"/admin/reports/DynamicGrid/view?listFormCode=@Name", IsVisible = true, VisibleExpression = "((e)=> e.row.data.CategoryId === 'Listeler')" }, + new() { + Hint = "App.Platform.View", + Text ="App.Platform.View", + UrlTarget="_blank", + AuthName = listFormName, + Url=$"/admin/reports/DynamicGrid/view?listFormCode=@Name&id=@Id", + IsVisible = true, + VisibleExpression = "((e)=> e.row.data.CategoryId === 'Formlar')" + }, }), } ); diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs index defe6b6..4702d17 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs @@ -3599,7 +3599,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency EditingOptionJson = DefaultEditingOptionJson(listFormName, 600, 450, true, true, true, true, false, true), EditingFormJson = JsonSerializer.Serialize(new List() { new() { - Order=1, ColCount=1, ColSpan=1, ItemType="group", Items= [ + Order=1, ColCount=2, ColSpan=1, ItemType="group", Items= [ new EditingFormItemDto { Order = 1, DataField = "CultureName", ColSpan = 1, EditorType2=EditorTypes.dxTextBox }, new EditingFormItemDto { Order = 2, DataField = "UiCultureName", ColSpan = 1, EditorType2=EditorTypes.dxTextBox }, new EditingFormItemDto { Order = 3, DataField = "DisplayName", ColSpan = 1, EditorType2=EditorTypes.dxTextBox }, diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json index 1c3b35e..0eda867 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json @@ -417,7 +417,7 @@ "authority": ["App.Files"] }, { - "key": "admin.devexpressReportView", + "key": "admin.devexpressDynamicReportView", "path": "/admin/reports/:id/view", "componentType": "normal", "componentPath": "@/views/report/DevexpressReportViewer", @@ -425,7 +425,7 @@ "authority": [] }, { - "key": "admin.devexpressReportDesigner", + "key": "admin.devexpressDynamicReportDesigner", "path": "/admin/reports/:id/design", "componentType": "normal", "componentPath": "@/views/report/DevexpressReportDesigner", diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json index 0845426..1b36603 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json @@ -1362,11 +1362,6 @@ } ], "ReportCategories": [ - { - "name": "Taahhütnameler", - "description": "Kursiyeler ile ilgili taahhütname raporları", - "icon": "✍️" - }, { "name": "Raporlar", "description": "Şirket içi genel tüm raporlar", @@ -1387,15 +1382,22 @@ { "name": "Standard Taahhütname", "description": "Standard Taahhütname", - "categoryName": "Taahhütnameler", - "htmlContent": "

Taahhütname

Bu belge, tarafların belirli yükümlülüklerini ve taahhütlerini belirtir.

", + "categoryName": "Raporlar", + "htmlContent": "

Taahhütname

http://localhost:3000/admin/reports/DynamicReport/view?id=ede12de7-0e94-4b84-a953-6c359606478b

", "status": "active" }, { - "name": "App.Definitions.AiBot", + "name": "App.Languages.LanguageText", "description": "Liste Raporları", "categoryName": "Listeler", - "htmlContent": "

Liste Raporları.

http://localhost:3000/admin/reports/DynamicListFormReport/view?listFormCode=App.Languages.LanguageText&filter=[%22CultureName%22,%22=%22,%22en%22]

", + "htmlContent": "

Liste Raporları.

http://localhost:3000/admin/reports/DynamicGridReport/view?listFormCode=App.Languages.LanguageText&filter=[%22CultureName%22,%22=%22,%22en%22]

", + "status": "active" + }, + { + "name": "App.Languages.Language", + "description": "Form Raporları", + "categoryName": "Formlar", + "htmlContent": "

Liste Raporları.

http://localhost:3000/admin/reports/DynamicFormReport/view?listFormCode=App.Languages.Language&id=e2c8a2e0-af22-f7bb-5d88-3a21e3079997

", "status": "active" } ], diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs new file mode 100644 index 0000000..60d194b --- /dev/null +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs @@ -0,0 +1,849 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Globalization; +using System.Linq; +using System.Text.Json; +using System.Text.RegularExpressions; +using DevExpress.DataAccess.ConnectionParameters; +using DevExpress.DataAccess.Sql; +using DevExpress.Drawing; +using DevExpress.XtraPrinting; +using DevExpress.XtraReports.UI; +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 DynamicFormReport : XtraReport +{ + public const string ReportName = nameof(DynamicFormReport); + private const string QueryName = "DynamicForm"; + private const int HorizontalMargin = 35; + private const float RowHeight = 24; + private static IServiceProvider _serviceProvider; + + public static void SetServiceProvider(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public static DynamicFormReport Create(IDictionary parameters) + { + var report = new DynamicFormReport(); + report.Build(parameters); + return report; + } + + public DynamicFormReport() + { + } + + private void Build(IDictionary parameters) + { + if (_serviceProvider == null) + { + throw new InvalidOperationException("DynamicFormReport service provider is not configured."); + } + + if (!parameters.TryGetValue("listFormCode", out var listFormCode) || string.IsNullOrWhiteSpace(listFormCode)) + { + throw new ArgumentException("listFormCode parameter is required."); + } + + if (!parameters.TryGetValue("id", out var id) || string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentException("id parameter is required."); + } + + using var scope = _serviceProvider.CreateScope(); + var authManager = scope.ServiceProvider.GetRequiredService(); + var listFormManager = scope.ServiceProvider.GetRequiredService(); + var listFormFieldManager = scope.ServiceProvider.GetRequiredService(); + var customizationManager = scope.ServiceProvider.GetRequiredService(); + var dynamicDataManager = scope.ServiceProvider.GetRequiredService(); + var selectQueryManager = scope.ServiceProvider.GetRequiredService(); + var defaultValueHelper = scope.ServiceProvider.GetRequiredService(); + var localizer = scope.ServiceProvider.GetRequiredService>(); + + if (!authManager.CanAccess(listFormCode).GetAwaiter().GetResult()) + { + throw new UnauthorizedAccessException($"No read permission for list form '{listFormCode}'."); + } + + var listForm = listFormManager.GetUserListForm(listFormCode).GetAwaiter().GetResult(); + var fields = listFormFieldManager.GetUserListFormFields(listFormCode).GetAwaiter().GetResult() + .Where(x => x.CanRead) + .OrderBy(x => x.ListOrderNo ?? int.MaxValue) + .ThenBy(x => x.CaptionName) + .ToList(); + var customizations = customizationManager.GetUsersServerCustomizations(listFormCode).GetAwaiter().GetResult(); + var (dynamicDataRepository, connectionString, dataSourceType) = dynamicDataManager + .GetAsync(listForm.IsTenant, listForm.DataSourceCode) + .GetAwaiter() + .GetResult(); + + var queryParams = new QueryParameters + { + ListFormCode = listFormCode, + Filter = GetParameter(parameters, "filter") ?? CreateKeyFilter(listForm.KeyFieldName, id), + Take = 1, + Skip = 0 + }; + + selectQueryManager.PrepareQueries(listForm, fields, dataSourceType, customizations, queryParams); + + var reportFields = GetReportFields(listForm, fields, selectQueryManager, defaultValueHelper); + var selectQuery = ApplyLookupQueryColumns( + selectQueryManager.SelectQuery, + listForm, + reportFields, + defaultValueHelper); + selectQuery = ApplyQueryParameters(selectQuery, selectQueryManager.SelectQueryParameters); + + var row = GetFirstRow(dynamicDataRepository.QueryAsync(selectQuery, connectionString).GetAwaiter().GetResult()); + + ConfigurePage(); + ConfigureDataSource(selectQuery, connectionString, dataSourceType); + BuildLayout(listForm, reportFields, row, localizer); + RequestParameters = false; + } + + private void ConfigurePage() + { + Margins = new System.Drawing.Printing.Margins(HorizontalMargin, HorizontalMargin, 35, 35); + PaperKind = DevExpress.Drawing.Printing.DXPaperKind.A4; + Landscape = false; + Font = new DXFont("Arial", 8.25F); + } + + private void ConfigureDataSource(string selectQuery, string connectionString, DataSourceTypeEnum dataSourceType) + { + var dataSource = new SqlDataSource(new CustomStringConnectionParameters(ToDevExpressConnectionString(connectionString, dataSourceType))); + dataSource.Queries.Add(new CustomSqlQuery(QueryName, selectQuery)); + ComponentStorage.Add(dataSource); + DataSource = dataSource; + DataMember = QueryName; + } + + private void BuildLayout( + ListForm listForm, + IReadOnlyList fields, + IDictionary row, + IStringLocalizer localizer) + { + Bands.Clear(); + + 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) }; + + Bands.AddRange([topMargin, reportHeader, detail, bottomMargin]); + + var pageWidth = PageWidth - Margins.Left - Margins.Right; + reportHeader.Controls.Add(new XRLabel + { + Text = 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 y = 0F; + foreach (var group in GetFormGroups(listForm, fields)) + { + if (!string.IsNullOrWhiteSpace(group.Caption)) + { + detail.Controls.Add(new XRLabel + { + Text = 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), + Padding = new PaddingInfo(5, 5, 2, 2), + TextAlignment = TextAlignment.MiddleLeft + }); + y += 26; + } + + foreach (var field in group.Fields) + { + var rowHeight = GetFieldRowHeight(field); + var labelWidth = pageWidth * 0.28F; + detail.Controls.Add(CreateCaptionLabel(field, localizer, 0, y, labelWidth, rowHeight)); + detail.Controls.Add(CreateValueLabel(field, labelWidth, y, pageWidth - labelWidth, rowHeight)); + y += rowHeight + 3; + } + + y += 8; + } + + foreach (var subForm in GetListSubForms(listForm, row)) + { + detail.Controls.Add(new XRSubreport + { + BoundsF = new System.Drawing.RectangleF(0, y, pageWidth, 24), + ReportSource = DynamicGridReport.Create(CreateSubReportParameters(subForm.Form, subForm.Filter)) + }); + y += 28; + } + + detail.HeightF = Math.Max(y, RowHeight); + } + + private static XRLabel CreateCaptionLabel( + FormReportField field, + IStringLocalizer localizer, + float x, + float y, + float width, + float height) + { + return new XRLabel + { + Text = 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), + Borders = BorderSide.All, + BorderWidth = 0.5F, + Padding = new PaddingInfo(5, 5, 2, 2), + TextAlignment = TextAlignment.MiddleLeft, + Multiline = true + }; + } + + private static XRLabel CreateValueLabel(FormReportField field, float x, float y, float width, float height) + { + var label = new XRLabel + { + BoundsF = new System.Drawing.RectangleF(x, y, width, height), + Font = new DXFont("Arial", 8F), + Borders = BorderSide.All, + BorderWidth = 0.5F, + Padding = new PaddingInfo(5, 5, 2, 2), + TextAlignment = ToTextAlignment(field.Field.Alignment, field.Field.SourceDbType), + Multiline = true, + CanGrow = true + }; + + label.ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", $"[{field.BindingName}]")); + + if (IsBooleanEditor(field) || field.StaticLookupValues?.Count > 0) + { + label.BeforePrint += (_, _) => + { + var value = Convert.ToString(label.Text, CultureInfo.InvariantCulture); + if (IsBooleanEditor(field) && TryFormatBoolean(value, out var booleanText)) + { + label.Text = booleanText; + } + else if (value != null && field.StaticLookupValues?.TryGetValue(value, out var displayText) == true) + { + label.Text = displayText; + } + }; + } + + return label; + } + + private static float CalculateDetailHeight( + IReadOnlyList fields, + ListForm listForm, + IDictionary row) + { + var groupHeight = GetFormGroups(listForm, fields) + .Sum(group => (string.IsNullOrWhiteSpace(group.Caption) ? 0 : 26) + + group.Fields.Sum(field => GetFieldRowHeight(field) + 3) + 8); + + var subFormHeight = GetListSubForms(listForm, row).Count() * 32; + return Math.Max(groupHeight + subFormHeight, RowHeight); + } + + private static float GetFieldRowHeight(FormReportField field) + { + return field.EditorType is PlatformConsts.EditorTypes.dxTextArea or PlatformConsts.EditorTypes.dxHtmlEditor + ? 48 + : RowHeight; + } + + private static List GetReportFields( + ListForm listForm, + List fields, + ISelectQueryManager selectQueryManager, + DefaultValueHelper defaultValueHelper) + { + var fieldsByName = fields + .Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined") + .Where(x => x.IsActive != false) + .ToDictionary(x => x.FieldName, StringComparer.OrdinalIgnoreCase); + + var formItems = GetEditingFormItems(listForm) + .Where(x => !string.IsNullOrWhiteSpace(x.DataField)) + .ToList(); + + var orderedFields = formItems.Count > 0 + ? formItems + .Where(x => fieldsByName.ContainsKey(x.DataField)) + .Select(x => new FieldWithEditor(fieldsByName[x.DataField], x.EditorType2)) + .ToList() + : fields + .Where(x => !string.IsNullOrWhiteSpace(x.FieldName) && x.FieldName != "undefined") + .Where(x => x.IsActive != false && x.Visible == true) + .Select(x => new FieldWithEditor(x, null)) + .ToList(); + + return orderedFields + .Where(x => selectQueryManager.SelectFields.Any(s => s.FieldName == x.Field.FieldName)) + .Select(x => + { + var selectField = selectQueryManager.SelectFields.First(s => s.FieldName == x.Field.FieldName); + return CreateFormReportField(x.Field, selectField, x.EditorType, defaultValueHelper); + }) + .Where(x => !string.IsNullOrWhiteSpace(x.BindingName) && x.BindingName != "undefined") + .ToList(); + } + + private static IEnumerable GetFormGroups(ListForm listForm, IReadOnlyList fields) + { + var fieldsByName = fields.ToDictionary(x => x.Field.FieldName, StringComparer.OrdinalIgnoreCase); + var groups = GetEditingFormGroups(listForm).ToList(); + if (groups.Count == 0) + { + yield return new FormGroup(null, fields); + yield break; + } + + var usedFields = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var group in groups) + { + var groupFields = (group.Items ?? []) + .OrderBy(x => x.Order) + .Where(x => !string.IsNullOrWhiteSpace(x.DataField) && fieldsByName.ContainsKey(x.DataField)) + .Select(x => fieldsByName[x.DataField]) + .ToList(); + + foreach (var field in groupFields) + { + usedFields.Add(field.Field.FieldName); + } + + if (groupFields.Count > 0) + { + yield return new FormGroup(group.Caption, groupFields); + } + } + + var remainingFields = fields + .Where(x => !usedFields.Contains(x.Field.FieldName)) + .ToList(); + if (remainingFields.Count > 0) + { + yield return new FormGroup(null, remainingFields); + } + } + + private static IEnumerable GetEditingFormGroups(ListForm listForm) + { + if (string.IsNullOrWhiteSpace(listForm.EditingFormJson)) + { + return []; + } + + try + { + return JsonSerializer.Deserialize>(listForm.EditingFormJson) ?? []; + } + catch (JsonException) + { + return []; + } + } + + private static IEnumerable GetEditingFormItems(ListForm listForm) + { + return GetEditingFormGroups(listForm) + .OrderBy(x => x.Order) + .SelectMany(x => (x.Items ?? []).OrderBy(i => i.Order)); + } + + private static IEnumerable<(SubFormDto Form, string Filter)> GetListSubForms( + ListForm listForm, + IDictionary row) + { + if (string.IsNullOrWhiteSpace(listForm.SubFormsJson) || row == null) + { + yield break; + } + + List subForms; + try + { + subForms = JsonSerializer.Deserialize>(listForm.SubFormsJson) ?? []; + } + catch (JsonException) + { + yield break; + } + + foreach (var subForm in subForms.Where(x => string.Equals(x.TabType, ListFormTypeEnum.List, StringComparison.OrdinalIgnoreCase))) + { + var relation = subForm.Relation? + .FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.ParentFieldName) && + !string.IsNullOrWhiteSpace(x.ChildFieldName) && + TryGetRowValue(row, x.ParentFieldName, out var value) && + value != null && + value != DBNull.Value); + + if (relation == null || !TryGetRowValue(row, relation.ParentFieldName, out var parentValue)) + { + continue; + } + + yield return (subForm, CreateFilter(relation.ChildFieldName, "=", parentValue)); + } + } + + private static Dictionary CreateSubReportParameters(SubFormDto subForm, string filter) + { + return new Dictionary + { + ["listFormCode"] = subForm.Code, + ["filter"] = filter + }; + } + + private static FormReportField CreateFormReportField( + ListFormField field, + SelectField selectField, + string editorType, + DefaultValueHelper defaultValueHelper) + { + var bindingName = GetReportFieldName(selectField); + var lookup = GetLookup(field); + + if (lookup == null || selectField?.JoinOptions != null) + { + return new FormReportField(field, bindingName, editorType, null, null); + } + + if (IsReportQueryableLookup(lookup)) + { + return new FormReportField(field, GetLookupBindingName(field), editorType, lookup, null); + } + + if (lookup.DataSourceType == UiLookupDataSourceTypeEnum.StaticData) + { + return new FormReportField( + field, + bindingName, + editorType, + lookup, + GetStaticLookupValues(lookup, defaultValueHelper)); + } + + return new FormReportField(field, bindingName, editorType, null, null); + } + + private static string CreateKeyFilter(string keyFieldName, string id) + { + return CreateFilter(keyFieldName, "=", id); + } + + private static string CreateFilter(string fieldName, string op, object value) + { + return JsonSerializer.Serialize(new object[] { fieldName, op, value }); + } + + private static IDictionary GetFirstRow(IEnumerable rows) + { + var row = rows?.FirstOrDefault(); + if (row is IDictionary dictionary) + { + return dictionary; + } + + 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 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 ApplyLookupQueryColumns( + string selectQuery, + ListForm listForm, + IReadOnlyList reportFields, + DefaultValueHelper defaultValueHelper) + { + var lookupFields = reportFields + .Where(x => x.Lookup != null && IsReportQueryableLookup(x.Lookup)) + .ToList(); + + if (lookupFields.Count == 0 || string.IsNullOrWhiteSpace(selectQuery)) + { + return selectQuery; + } + + var tableAlias = string.IsNullOrWhiteSpace(listForm.TableName) + ? listForm.SelectCommand + : listForm.TableName; + + var selectParts = new List(); + var joinParts = new List(); + foreach (var field in lookupFields) + { + var lookup = field.Lookup; + var alias = GetLookupAlias(field.Field); + var valueExpr = QuoteSqlIdentifier(lookup.ValueExpr ?? "Key"); + var displayExpr = QuoteSqlIdentifier(lookup.DisplayExpr ?? "Name"); + var lookupQuery = NormalizeLookupQuery(defaultValueHelper.GetDefaultValue(lookup.LookupQuery)); + + selectParts.Add($"{alias}.{displayExpr} AS {QuoteSqlIdentifier(field.BindingName)}"); + joinParts.Add( + $"LEFT JOIN ({lookupQuery}) AS {alias} " + + $"ON {alias}.{valueExpr} = {QuoteSqlIdentifier(tableAlias)}.{QuoteSqlIdentifier(field.Field.FieldName)}"); + } + + var fromIndex = selectQuery.IndexOf(" FROM ", StringComparison.OrdinalIgnoreCase); + if (fromIndex < 0) + { + return selectQuery; + } + + selectQuery = selectQuery.Insert(fromIndex, "," + string.Join(',', selectParts)); + + var joinInsertIndex = FindClauseIndex(selectQuery, " WHERE "); + if (joinInsertIndex < 0) + { + joinInsertIndex = FindClauseIndex(selectQuery, " ORDER BY "); + } + if (joinInsertIndex < 0) + { + return selectQuery + " " + string.Join(' ', joinParts); + } + + return selectQuery.Insert(joinInsertIndex, " " + string.Join(' ', joinParts)); + } + + private static bool IsReportQueryableLookup(LookupDto lookup) + { + return lookup.DataSourceType == UiLookupDataSourceTypeEnum.Query && + string.IsNullOrWhiteSpace(lookup.CascadeParentFields) && + !string.IsNullOrWhiteSpace(lookup.LookupQuery) && + !Regex.IsMatch(lookup.LookupQuery, @"@\s*param\d+", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + + private static int FindClauseIndex(string sql, string clause) + { + return sql.IndexOf(clause, StringComparison.OrdinalIgnoreCase); + } + + private static string NormalizeLookupQuery(string lookupQuery) + { + var query = lookupQuery.Trim().TrimEnd(';'); + return Regex.Replace( + query, + @"\s+ORDER\s+BY\s+[\s\S]*$", + string.Empty, + RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } + + private static string GetLookupBindingName(ListFormField field) + { + return $"{field.FieldName}__LookupText"; + } + + private static string GetLookupAlias(ListFormField field) + { + return QuoteSqlIdentifier($"Lookup_{Regex.Replace(field.FieldName, @"\W+", "_")}"); + } + + private static string QuoteSqlIdentifier(string identifier) + { + return $"\"{identifier.Replace("\"", "\"\"")}\""; + } + + 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 || + field.Field.SourceDbType == DbType.Boolean; + } + + private static bool TryFormatBoolean(string value, out string text) + { + if (bool.TryParse(value, out var boolean)) + { + text = boolean ? "Evet" : "Hayır"; + return true; + } + + if (value == "1" || string.Equals(value, "true", StringComparison.OrdinalIgnoreCase)) + { + text = "Evet"; + return true; + } + + if (value == "0" || string.Equals(value, "false", StringComparison.OrdinalIgnoreCase)) + { + text = "Hayır"; + return true; + } + + text = null; + return false; + } + + private static string GetParameter(IDictionary parameters, string name) + { + return parameters.TryGetValue(name, out var value) ? value : null; + } + + private sealed record FieldWithEditor(ListFormField Field, string EditorType); + private sealed record FormGroup(string Caption, IReadOnlyList Fields); + private sealed record FormReportField( + ListFormField Field, + string BindingName, + string EditorType, + LookupDto Lookup, + Dictionary StaticLookupValues); +} diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicListFormReport.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicGridReport.cs similarity index 98% rename from api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicListFormReport.cs rename to api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicGridReport.cs index 5d367b3..9957b59 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicListFormReport.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicGridReport.cs @@ -21,10 +21,10 @@ using Sozsoft.Platform.Queries; namespace Sozsoft.Reports.PredefinedReports; -public class DynamicListFormReport : XtraReport +public class DynamicGridReport : XtraReport { - public const string ReportName = nameof(DynamicListFormReport); - private const string QueryName = "DynamicList"; + public const string ReportName = nameof(DynamicGridReport); + private const string QueryName = "DynamicGrid"; private const int A4PortraitWidth = 827; private const int HorizontalMargin = 35; private const int LandscapeColumnThreshold = 5; @@ -35,14 +35,14 @@ public class DynamicListFormReport : XtraReport _serviceProvider = serviceProvider; } - public static DynamicListFormReport Create(IDictionary parameters) + public static DynamicGridReport Create(IDictionary parameters) { - var report = new DynamicListFormReport(); + var report = new DynamicGridReport(); report.Build(parameters); return report; } - public DynamicListFormReport() + public DynamicGridReport() { } diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TemplateReport.Designer.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReport.Designer.cs similarity index 99% rename from api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TemplateReport.Designer.cs rename to api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReport.Designer.cs index 8593c03..ed55324 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TemplateReport.Designer.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReport.Designer.cs @@ -3,7 +3,7 @@ using Sozsoft.Platform.Enums; namespace Sozsoft.Reports.PredefinedReports { - partial class TemplateReport + partial class DynamicReport { /// /// Required designer variable. @@ -36,7 +36,7 @@ namespace Sozsoft.Reports.PredefinedReports DevExpress.DataAccess.Sql.Column column1 = new DevExpress.DataAccess.Sql.Column(); DevExpress.DataAccess.Sql.ColumnExpression columnExpression1 = new DevExpress.DataAccess.Sql.ColumnExpression(); DevExpress.DataAccess.Sql.Table table1 = new DevExpress.DataAccess.Sql.Table(); - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TemplateReport)); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DynamicReport)); this.TopMargin = new DevExpress.XtraReports.UI.TopMarginBand(); this.BottomMargin = new DevExpress.XtraReports.UI.BottomMarginBand(); this.pageInfo1 = new DevExpress.XtraReports.UI.XRPageInfo(); diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TemplateReport.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReport.cs similarity index 96% rename from api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TemplateReport.cs rename to api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReport.cs index aaa6826..da5f364 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TemplateReport.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReport.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.DependencyInjection; namespace Sozsoft.Reports.PredefinedReports; -public partial class TemplateReport : XtraReport +public partial class DynamicReport : XtraReport { private Dictionary _parameters; private static IServiceProvider _serviceProvider; @@ -22,7 +22,7 @@ public partial class TemplateReport : XtraReport _serviceProvider = serviceProvider; } - public TemplateReport() + public DynamicReport() { InitializeComponent(); diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TemplateReport.resx b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReport.resx similarity index 100% rename from api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TemplateReport.resx rename to api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReport.resx diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TestReport.Designer.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TestReport.Designer.cs deleted file mode 100644 index 36448e7..0000000 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TestReport.Designer.cs +++ /dev/null @@ -1,264 +0,0 @@ -using Sozsoft.Platform.Enums; - -namespace Sozsoft.Reports.PredefinedReports -{ - partial class TestReport - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.components = new System.ComponentModel.Container(); - DevExpress.DataAccess.Sql.SelectQuery selectQuery1 = new DevExpress.DataAccess.Sql.SelectQuery(); - DevExpress.DataAccess.Sql.Column column1 = new DevExpress.DataAccess.Sql.Column(); - DevExpress.DataAccess.Sql.ColumnExpression columnExpression1 = new DevExpress.DataAccess.Sql.ColumnExpression(); - DevExpress.DataAccess.Sql.Table table1 = new DevExpress.DataAccess.Sql.Table(); - DevExpress.DataAccess.Sql.Column column2 = new DevExpress.DataAccess.Sql.Column(); - DevExpress.DataAccess.Sql.ColumnExpression columnExpression2 = new DevExpress.DataAccess.Sql.ColumnExpression(); - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TemplateReport)); - this.TopMargin = new DevExpress.XtraReports.UI.TopMarginBand(); - this.BottomMargin = new DevExpress.XtraReports.UI.BottomMarginBand(); - this.pageInfo1 = new DevExpress.XtraReports.UI.XRPageInfo(); - this.pageInfo2 = new DevExpress.XtraReports.UI.XRPageInfo(); - this.ReportHeader = new DevExpress.XtraReports.UI.ReportHeaderBand(); - this.label1 = new DevExpress.XtraReports.UI.XRLabel(); - this.Detail = new DevExpress.XtraReports.UI.DetailBand(); - this.table2 = new DevExpress.XtraReports.UI.XRTable(); - this.tableRow1 = new DevExpress.XtraReports.UI.XRTableRow(); - this.tableCell1 = new DevExpress.XtraReports.UI.XRTableCell(); - this.tableCell2 = new DevExpress.XtraReports.UI.XRTableCell(); - this.sqlDataSource1 = new DevExpress.DataAccess.Sql.SqlDataSource(this.components); - this.Title = new DevExpress.XtraReports.UI.XRControlStyle(); - this.DetailCaption1 = new DevExpress.XtraReports.UI.XRControlStyle(); - this.DetailData1 = new DevExpress.XtraReports.UI.XRControlStyle(); - this.DetailData3_Odd = new DevExpress.XtraReports.UI.XRControlStyle(); - this.PageInfo = new DevExpress.XtraReports.UI.XRControlStyle(); - ((System.ComponentModel.ISupportInitialize)(this.table2)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this)).BeginInit(); - // - // TopMargin - // - this.TopMargin.Name = "TopMargin"; - // - // BottomMargin - // - this.BottomMargin.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { - this.pageInfo1, - this.pageInfo2}); - this.BottomMargin.Name = "BottomMargin"; - // - // pageInfo1 - // - this.pageInfo1.LocationFloat = new DevExpress.Utils.PointFloat(0F, 0F); - this.pageInfo1.Name = "pageInfo1"; - this.pageInfo1.PageInfo = DevExpress.XtraPrinting.PageInfo.DateTime; - this.pageInfo1.SizeF = new System.Drawing.SizeF(325F, 23F); - this.pageInfo1.StyleName = "PageInfo"; - // - // pageInfo2 - // - this.pageInfo2.LocationFloat = new DevExpress.Utils.PointFloat(325F, 0F); - this.pageInfo2.Name = "pageInfo2"; - this.pageInfo2.SizeF = new System.Drawing.SizeF(325F, 23F); - this.pageInfo2.StyleName = "PageInfo"; - this.pageInfo2.TextAlignment = DevExpress.XtraPrinting.TextAlignment.TopRight; - this.pageInfo2.TextFormatString = "Page {0} of {1}"; - // - // ReportHeader - // - this.ReportHeader.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { - this.label1}); - this.ReportHeader.HeightF = 60F; - this.ReportHeader.Name = "ReportHeader"; - // - // label1 - // - this.label1.LocationFloat = new DevExpress.Utils.PointFloat(0F, 0F); - this.label1.Name = "label1"; - this.label1.SizeF = new System.Drawing.SizeF(650F, 24.19433F); - this.label1.StyleName = "Title"; - this.label1.Text = "Sector Report"; - // - // Detail - // - this.Detail.Controls.AddRange(new DevExpress.XtraReports.UI.XRControl[] { - this.table2}); - this.Detail.HeightF = 25F; - this.Detail.Name = "Detail"; - // - // table2 - // - this.table2.LocationFloat = new DevExpress.Utils.PointFloat(0F, 0F); - this.table2.Name = "table2"; - this.table2.OddStyleName = "DetailData3_Odd"; - this.table2.Rows.AddRange(new DevExpress.XtraReports.UI.XRTableRow[] { - this.tableRow1}); - this.table2.SizeF = new System.Drawing.SizeF(650F, 25F); - // - // tableRow1 - // - this.tableRow1.Cells.AddRange(new DevExpress.XtraReports.UI.XRTableCell[] { - this.tableCell1, - this.tableCell2}); - this.tableRow1.Name = "tableRow1"; - this.tableRow1.Weight = 1D; - // - // tableCell1 - // - this.tableCell1.Borders = DevExpress.XtraPrinting.BorderSide.None; - this.tableCell1.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { - new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[Id]")}); - this.tableCell1.Name = "tableCell1"; - this.tableCell1.StyleName = "DetailData1"; - this.tableCell1.StylePriority.UseBorders = false; - this.tableCell1.Text = "Id"; - this.tableCell1.Weight = 0.3D; - // - // tableCell2 - // - this.tableCell2.ExpressionBindings.AddRange(new DevExpress.XtraReports.UI.ExpressionBinding[] { - new DevExpress.XtraReports.UI.ExpressionBinding("BeforePrint", "Text", "[Name]")}); - this.tableCell2.Name = "tableCell2"; - this.tableCell2.StyleName = "DetailData1"; - this.tableCell2.Text = "Name"; - this.tableCell2.Weight = 0.7D; - // - // sqlDataSource1 - // - this.sqlDataSource1.ConnectionName = "SqlServer"; - this.sqlDataSource1.Name = "sqlDataSource1"; - columnExpression1.ColumnName = "Id"; - table1.Name = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Sector)); - columnExpression1.Table = table1; - column1.Expression = columnExpression1; - columnExpression2.ColumnName = "Name"; - columnExpression2.Table = table1; - column2.Expression = columnExpression2; - selectQuery1.Columns.Add(column1); - selectQuery1.Columns.Add(column2); - selectQuery1.Name = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Sector)); - selectQuery1.Tables.Add(table1); - this.sqlDataSource1.Queries.AddRange(new DevExpress.DataAccess.Sql.SqlQuery[] { - selectQuery1}); - this.sqlDataSource1.ResultSchemaSerializable = resources.GetString("sqlDataSource1.ResultSchemaSerializable"); - // - // Title - // - this.Title.BackColor = System.Drawing.Color.Transparent; - this.Title.BorderColor = System.Drawing.Color.Black; - this.Title.Borders = DevExpress.XtraPrinting.BorderSide.None; - this.Title.BorderWidth = 1F; - this.Title.Font = new DevExpress.Drawing.DXFont("Arial", 14.25F); - this.Title.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(70)))), ((int)(((byte)(80))))); - this.Title.Name = "Title"; - this.Title.Padding = new DevExpress.XtraPrinting.PaddingInfo(6, 6, 0, 0, 100F); - // - // DetailCaption1 - // - this.DetailCaption1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(181)))), ((int)(((byte)(211)))), ((int)(((byte)(142))))); - this.DetailCaption1.BorderColor = System.Drawing.Color.White; - this.DetailCaption1.Borders = DevExpress.XtraPrinting.BorderSide.Left; - this.DetailCaption1.BorderWidth = 2F; - this.DetailCaption1.Font = new DevExpress.Drawing.DXFont("Arial", 8.25F, DevExpress.Drawing.DXFontStyle.Bold); - this.DetailCaption1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(70)))), ((int)(((byte)(80))))); - this.DetailCaption1.Name = "DetailCaption1"; - this.DetailCaption1.Padding = new DevExpress.XtraPrinting.PaddingInfo(6, 6, 0, 0, 100F); - this.DetailCaption1.TextAlignment = DevExpress.XtraPrinting.TextAlignment.MiddleLeft; - // - // DetailData1 - // - this.DetailData1.BorderColor = System.Drawing.Color.Transparent; - this.DetailData1.Borders = DevExpress.XtraPrinting.BorderSide.Left; - this.DetailData1.BorderWidth = 2F; - this.DetailData1.Font = new DevExpress.Drawing.DXFont("Arial", 8.25F); - this.DetailData1.ForeColor = System.Drawing.Color.Black; - this.DetailData1.Name = "DetailData1"; - this.DetailData1.Padding = new DevExpress.XtraPrinting.PaddingInfo(6, 6, 0, 0, 100F); - this.DetailData1.TextAlignment = DevExpress.XtraPrinting.TextAlignment.MiddleLeft; - // - // DetailData3_Odd - // - this.DetailData3_Odd.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(243)))), ((int)(((byte)(245)))), ((int)(((byte)(248))))); - this.DetailData3_Odd.BorderColor = System.Drawing.Color.Transparent; - this.DetailData3_Odd.Borders = DevExpress.XtraPrinting.BorderSide.None; - this.DetailData3_Odd.BorderWidth = 1F; - this.DetailData3_Odd.Font = new DevExpress.Drawing.DXFont("Arial", 8.25F); - this.DetailData3_Odd.ForeColor = System.Drawing.Color.Black; - this.DetailData3_Odd.Name = "DetailData3_Odd"; - this.DetailData3_Odd.Padding = new DevExpress.XtraPrinting.PaddingInfo(6, 6, 0, 0, 100F); - this.DetailData3_Odd.TextAlignment = DevExpress.XtraPrinting.TextAlignment.MiddleLeft; - // - // PageInfo - // - this.PageInfo.Font = new DevExpress.Drawing.DXFont("Arial", 8.25F, DevExpress.Drawing.DXFontStyle.Bold); - this.PageInfo.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(70)))), ((int)(((byte)(80))))); - this.PageInfo.Name = "PageInfo"; - this.PageInfo.Padding = new DevExpress.XtraPrinting.PaddingInfo(6, 6, 0, 0, 100F); - // - // TestReport - // - this.Bands.AddRange(new DevExpress.XtraReports.UI.Band[] { - this.TopMargin, - this.BottomMargin, - this.ReportHeader, - this.Detail}); - this.ComponentStorage.AddRange(new System.ComponentModel.IComponent[] { - this.sqlDataSource1}); - this.DataMember = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Sector)); - this.DataSource = this.sqlDataSource1; - this.Font = new DevExpress.Drawing.DXFont("Arial", 9.75F); - this.StyleSheet.AddRange(new DevExpress.XtraReports.UI.XRControlStyle[] { - this.Title, - this.DetailCaption1, - this.DetailData1, - this.DetailData3_Odd, - this.PageInfo}); - this.Version = "22.2"; - ((System.ComponentModel.ISupportInitialize)(this.table2)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this)).EndInit(); - - } - - #endregion - - private DevExpress.XtraReports.UI.TopMarginBand TopMargin; - private DevExpress.XtraReports.UI.BottomMarginBand BottomMargin; - private DevExpress.XtraReports.UI.XRPageInfo pageInfo1; - private DevExpress.XtraReports.UI.XRPageInfo pageInfo2; - private DevExpress.XtraReports.UI.ReportHeaderBand ReportHeader; - private DevExpress.XtraReports.UI.XRLabel label1; - private DevExpress.XtraReports.UI.DetailBand Detail; - private DevExpress.XtraReports.UI.XRTable table2; - private DevExpress.XtraReports.UI.XRTableRow tableRow1; - private DevExpress.XtraReports.UI.XRTableCell tableCell1; - private DevExpress.XtraReports.UI.XRTableCell tableCell2; - private DevExpress.DataAccess.Sql.SqlDataSource sqlDataSource1; - private DevExpress.XtraReports.UI.XRControlStyle Title; - private DevExpress.XtraReports.UI.XRControlStyle DetailCaption1; - private DevExpress.XtraReports.UI.XRControlStyle DetailData1; - private DevExpress.XtraReports.UI.XRControlStyle DetailData3_Odd; - private DevExpress.XtraReports.UI.XRControlStyle PageInfo; - } -} diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TestReport.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TestReport.cs deleted file mode 100644 index bd8c524..0000000 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TestReport.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Sozsoft.Reports.PredefinedReports; - -public partial class TestReport : DevExpress.XtraReports.UI.XtraReport -{ - public TestReport() - { - InitializeComponent(); - } -} diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TestReport.resx b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TestReport.resx deleted file mode 100644 index e36faff..0000000 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/TestReport.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - PERhdGFTZXQgTmFtZT0ic3FsRGF0YVNvdXJjZTEiPjxWaWV3IE5hbWU9IkNhdGVnb3JpZXMiPjxGaWVsZCBOYW1lPSJDYXRlZ29yeUlEIiBUeXBlPSJJbnQ2NCIgLz48RmllbGQgTmFtZT0iQ2F0ZWdvcnlOYW1lIiBUeXBlPSJTdHJpbmciIC8+PEZpZWxkIE5hbWU9IkRlc2NyaXB0aW9uIiBUeXBlPSJTdHJpbmciIC8+PEZpZWxkIE5hbWU9IlBpY3R1cmUiIFR5cGU9IkJ5dGVBcnJheSIgLz48RmllbGQgTmFtZT0iSWNvbjE3IiBUeXBlPSJCeXRlQXJyYXkiIC8+PEZpZWxkIE5hbWU9Ikljb24yNSIgVHlwZT0iQnl0ZUFycmF5IiAvPjwvVmlldz48L0RhdGFTZXQ+ - - \ No newline at end of file diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/ReportServices/CustomReportStorageWebExtension.cs b/api/src/Sozsoft.Platform.HttpApi.Host/ReportServices/CustomReportStorageWebExtension.cs index 90acbf2..f1c06a9 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/ReportServices/CustomReportStorageWebExtension.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/ReportServices/CustomReportStorageWebExtension.cs @@ -19,9 +19,10 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens { _serviceProvider = serviceProvider; - // TemplateReport için ServiceProvider'ı set et - Sozsoft.Reports.PredefinedReports.TemplateReport.SetServiceProvider(serviceProvider); - Sozsoft.Reports.PredefinedReports.DynamicListFormReport.SetServiceProvider(serviceProvider); + // DynamicReport için ServiceProvider'ı set et + Sozsoft.Reports.PredefinedReports.DynamicReport.SetServiceProvider(serviceProvider); + Sozsoft.Reports.PredefinedReports.DynamicGridReport.SetServiceProvider(serviceProvider); + Sozsoft.Reports.PredefinedReports.DynamicFormReport.SetServiceProvider(serviceProvider); if (!Directory.Exists(reportDirectory)) { @@ -33,9 +34,10 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens { _serviceProvider = serviceProvider; - // TemplateReport için ServiceProvider'ı set et - Sozsoft.Reports.PredefinedReports.TemplateReport.SetServiceProvider(serviceProvider); - Sozsoft.Reports.PredefinedReports.DynamicListFormReport.SetServiceProvider(serviceProvider); + // DynamicReport için ServiceProvider'ı set et + Sozsoft.Reports.PredefinedReports.DynamicReport.SetServiceProvider(serviceProvider); + Sozsoft.Reports.PredefinedReports.DynamicGridReport.SetServiceProvider(serviceProvider); + Sozsoft.Reports.PredefinedReports.DynamicFormReport.SetServiceProvider(serviceProvider); if (!Directory.Exists(reportDirectory)) { @@ -81,10 +83,20 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens try { var (reportName, parameters) = ParseReportUrl(url); - - if (reportName == Sozsoft.Reports.PredefinedReports.DynamicListFormReport.ReportName) + + if (IsDynamicGridReport(reportName)) { - var report = Sozsoft.Reports.PredefinedReports.DynamicListFormReport.Create(parameters); + XtraReport report = HasParameter(parameters, "id") + ? Sozsoft.Reports.PredefinedReports.DynamicFormReport.Create(parameters) + : Sozsoft.Reports.PredefinedReports.DynamicGridReport.Create(parameters); + using MemoryStream ms = new MemoryStream(); + report.SaveLayoutToXml(ms); + return ms.ToArray(); + } + + if (IsDynamicFormReport(reportName)) + { + var report = Sozsoft.Reports.PredefinedReports.DynamicFormReport.Create(parameters); using MemoryStream ms = new MemoryStream(); report.SaveLayoutToXml(ms); return ms.ToArray(); @@ -171,7 +183,7 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens { var decodedUrl = DecodeRepeatedly(url); var reportName = decodedUrl; - var parameters = new Dictionary(); + var parameters = new Dictionary(StringComparer.OrdinalIgnoreCase); var queryIndex = decodedUrl.IndexOf('?'); if (queryIndex < 0) @@ -195,6 +207,23 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens return (reportName, parameters); } + private static bool IsDynamicGridReport(string reportName) + { + return string.Equals(reportName, "DynamicGrid", StringComparison.OrdinalIgnoreCase) || + string.Equals(reportName, Sozsoft.Reports.PredefinedReports.DynamicGridReport.ReportName, StringComparison.OrdinalIgnoreCase); + } + + private static bool IsDynamicFormReport(string reportName) + { + return string.Equals(reportName, "DynamicForm", StringComparison.OrdinalIgnoreCase) || + string.Equals(reportName, Sozsoft.Reports.PredefinedReports.DynamicFormReport.ReportName, StringComparison.OrdinalIgnoreCase); + } + + private static bool HasParameter(Dictionary parameters, string name) + { + return parameters.TryGetValue(name, out var value) && !string.IsNullOrWhiteSpace(value); + } + private static string DecodeRepeatedly(string value) { var decoded = value; diff --git a/ui/src/views/report/DevexpressReportViewer.tsx b/ui/src/views/report/DevexpressReportViewer.tsx index d6e3c64..b6e56cc 100644 --- a/ui/src/views/report/DevexpressReportViewer.tsx +++ b/ui/src/views/report/DevexpressReportViewer.tsx @@ -84,12 +84,17 @@ const loadViewerCss = (isDarkMode: boolean) => { const DevexpressReportViewer: React.FC = () => { const { translate } = useLocalization() - const { id } = useParams<{ id: string }>() + const { id, listFormCode, recordId } = useParams<{ + id: string + listFormCode: string + recordId: string + }>() const location = useLocation() const token = useStoreState((state) => state.auth.session.token) const themeMode = useStoreState((state) => state.theme.mode) const [isRequestAuthReady, setIsRequestAuthReady] = useState(false) const isDarkMode = themeMode === MODE_DARK + const formRecordId = recordId ?? (listFormCode ? id : undefined) useEffect(() => { loadViewerCss(isDarkMode) @@ -116,11 +121,24 @@ const DevexpressReportViewer: React.FC = () => { }, [token]) const reportUrlWithParams = useMemo(() => { + const searchParams = new URLSearchParams(location.search) + + if (listFormCode) { + searchParams.set('listFormCode', listFormCode) + + if (formRecordId) { + searchParams.set('id', formRecordId) + return `DynamicFormReport?${searchParams.toString()}` + } + + return `DynamicListFormReport?${searchParams.toString()}` + } + if (!id) return '' return location.search ? `${id}${location.search}` : id - }, [id, location.search]) + }, [id, listFormCode, formRecordId, location.search]) - if (!id) return null + if (!reportUrlWithParams) return null return (