Devexpress Grid Raporu otomatik oluşturma
This commit is contained in:
parent
3cff822775
commit
6884812484
7 changed files with 594 additions and 38 deletions
|
|
@ -1586,6 +1586,7 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
AuthName = listFormName,
|
AuthName = listFormName,
|
||||||
Url=$"/admin/reports/TemplateReport/view?id=@Id",
|
Url=$"/admin/reports/TemplateReport/view?id=@Id",
|
||||||
IsVisible = true,
|
IsVisible = true,
|
||||||
|
VisibleExpression = "((e)=> e.row.data.Description !== 'Liste Raporları')"
|
||||||
},
|
},
|
||||||
new() {
|
new() {
|
||||||
Hint = "App.Platform.Design",
|
Hint = "App.Platform.Design",
|
||||||
|
|
@ -1594,6 +1595,16 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
AuthName = listFormName + ".Update",
|
AuthName = listFormName + ".Update",
|
||||||
Url=$"/admin/reports/TemplateReport/design?id=@Id",
|
Url=$"/admin/reports/TemplateReport/design?id=@Id",
|
||||||
IsVisible = true,
|
IsVisible = true,
|
||||||
|
VisibleExpression = "((e)=> e.row.data.Description !== 'Liste Raporları')"
|
||||||
|
},
|
||||||
|
new() {
|
||||||
|
Hint = "App.Platform.View",
|
||||||
|
Text ="App.Platform.View",
|
||||||
|
UrlTarget="_blank",
|
||||||
|
AuthName = listFormName,
|
||||||
|
Url=$"/admin/reports/DynamicListFormReport/view?listFormCode=@Name",
|
||||||
|
IsVisible = true,
|
||||||
|
VisibleExpression = "((e)=> e.row.data.Description !== 'Liste Raporları')"
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1373,8 +1373,8 @@
|
||||||
"icon": "✍️"
|
"icon": "✍️"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Sözleşmeler",
|
"name": "Listeler",
|
||||||
"description": "Tedarikçiler ile ilgili sözleşme raporları",
|
"description": "Tüm listelerin raporları",
|
||||||
"icon": "📜"
|
"icon": "📜"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -1384,7 +1384,14 @@
|
||||||
"description": "Standard Taahhütname",
|
"description": "Standard Taahhütname",
|
||||||
"categoryName": "Taahhütnameler",
|
"categoryName": "Taahhütnameler",
|
||||||
"htmlContent": "<h1>Taahhütname</h1><p>Bu belge, tarafların belirli yükümlülüklerini ve taahhütlerini belirtir.</p>",
|
"htmlContent": "<h1>Taahhütname</h1><p>Bu belge, tarafların belirli yükümlülüklerini ve taahhütlerini belirtir.</p>",
|
||||||
"status": "Aktif"
|
"status": "active"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "App.Definitions.AiBot",
|
||||||
|
"description": "Liste Raporları",
|
||||||
|
"categoryName": "Listeler",
|
||||||
|
"htmlContent": "<p>Liste Raporları.</p>",
|
||||||
|
"status": "active"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"WorkHours": [
|
"WorkHours": [
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
using DevExpress.AspNetCore.Reporting.WebDocumentViewer;
|
using DevExpress.AspNetCore.Reporting.WebDocumentViewer;
|
||||||
using DevExpress.AspNetCore.Reporting.WebDocumentViewer.Native.Services;
|
using DevExpress.AspNetCore.Reporting.WebDocumentViewer.Native.Services;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace Sozsoft.Platform.Controllers;
|
namespace Sozsoft.Platform.Controllers;
|
||||||
|
|
||||||
[Route("DXXRDV")]
|
[Route("DXXRDV")]
|
||||||
|
[Authorize]
|
||||||
[ApiExplorerSettings(IgnoreApi = true)]
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
public class CustomWebDocumentViewerController : WebDocumentViewerController
|
public class CustomWebDocumentViewerController : WebDocumentViewerController
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,480 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
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 Microsoft.Extensions.Primitives;
|
||||||
|
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 DynamicListFormReport : XtraReport
|
||||||
|
{
|
||||||
|
public const string ReportName = nameof(DynamicListFormReport);
|
||||||
|
private const string QueryName = "DynamicList";
|
||||||
|
private const int A4PortraitWidth = 827;
|
||||||
|
private const int HorizontalMargin = 35;
|
||||||
|
private const int LandscapeColumnThreshold = 5;
|
||||||
|
private static IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
public static void SetServiceProvider(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DynamicListFormReport Create(IDictionary<string, string> parameters)
|
||||||
|
{
|
||||||
|
var report = new DynamicListFormReport();
|
||||||
|
report.Build(parameters);
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DynamicListFormReport()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Build(IDictionary<string, string> parameters)
|
||||||
|
{
|
||||||
|
if (_serviceProvider == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("DynamicListFormReport service provider is not configured.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parameters.TryGetValue("listFormCode", out var listFormCode) || string.IsNullOrWhiteSpace(listFormCode))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("listFormCode parameter is required.");
|
||||||
|
}
|
||||||
|
|
||||||
|
using var scope = _serviceProvider.CreateScope();
|
||||||
|
var authManager = scope.ServiceProvider.GetRequiredService<IListFormAuthorizationManager>();
|
||||||
|
var listFormManager = scope.ServiceProvider.GetRequiredService<IListFormManager>();
|
||||||
|
var listFormFieldManager = scope.ServiceProvider.GetRequiredService<IListFormFieldManager>();
|
||||||
|
var customizationManager = scope.ServiceProvider.GetRequiredService<IListFormCustomizationManager>();
|
||||||
|
var dynamicDataManager = scope.ServiceProvider.GetRequiredService<IDynamicDataManager>();
|
||||||
|
var selectQueryManager = scope.ServiceProvider.GetRequiredService<ISelectQueryManager>();
|
||||||
|
var localizer = scope.ServiceProvider.GetRequiredService<IStringLocalizer<PlatformResource>>();
|
||||||
|
|
||||||
|
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 (_, connectionString, dataSourceType) = dynamicDataManager.GetAsync(listForm.IsTenant, listForm.DataSourceCode).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
var queryParameters = ToStringValues(parameters);
|
||||||
|
var queryParams = new QueryParameters
|
||||||
|
{
|
||||||
|
ListFormCode = listFormCode,
|
||||||
|
Filter = GetParameter(parameters, "filter"),
|
||||||
|
Sort = GetParameter(parameters, "sort"),
|
||||||
|
Group = GetParameter(parameters, "group"),
|
||||||
|
GroupSummary = GetParameter(parameters, "groupSummary"),
|
||||||
|
TotalSummary = GetParameter(parameters, "totalSummary"),
|
||||||
|
Take = 0,
|
||||||
|
Skip = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
selectQueryManager.PrepareQueries(listForm, fields, dataSourceType, customizations, queryParams);
|
||||||
|
|
||||||
|
var 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))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (visibleFields.Count == 0)
|
||||||
|
{
|
||||||
|
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))
|
||||||
|
.Take(8)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
var reportColumns = visibleFields
|
||||||
|
.Select(field =>
|
||||||
|
{
|
||||||
|
var selectField = selectQueryManager.SelectFields.First(s => s.FieldName == field.FieldName);
|
||||||
|
return new ReportColumn(field, GetReportFieldName(selectField));
|
||||||
|
})
|
||||||
|
.Where(x => !string.IsNullOrWhiteSpace(x.BindingName) && x.BindingName != "undefined")
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
ConfigurePage(reportColumns);
|
||||||
|
ConfigureDataSource(
|
||||||
|
ApplyQueryParameters(selectQueryManager.SelectQuery, selectQueryManager.SelectQueryParameters),
|
||||||
|
connectionString,
|
||||||
|
dataSourceType);
|
||||||
|
BuildLayout(listForm, reportColumns, localizer);
|
||||||
|
RequestParameters = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfigurePage(IReadOnlyCollection<ReportColumn> visibleFields)
|
||||||
|
{
|
||||||
|
Margins = new System.Drawing.Printing.Margins(HorizontalMargin, HorizontalMargin, 35, 35);
|
||||||
|
PaperKind = DevExpress.Drawing.Printing.DXPaperKind.A4;
|
||||||
|
var portraitContentWidth = A4PortraitWidth - Margins.Left - Margins.Right;
|
||||||
|
var requestedContentWidth = visibleFields.Sum(x => Math.Max(x.Field.Width ?? 90, 60));
|
||||||
|
Landscape = visibleFields.Count >= LandscapeColumnThreshold || requestedContentWidth > portraitContentWidth;
|
||||||
|
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 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<string, object> 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 void BuildLayout(
|
||||||
|
ListForm listForm,
|
||||||
|
IReadOnlyList<ReportColumn> fields,
|
||||||
|
IStringLocalizer<PlatformResource> localizer)
|
||||||
|
{
|
||||||
|
Bands.Clear();
|
||||||
|
|
||||||
|
var topMargin = new TopMarginBand { HeightF = Margins.Top };
|
||||||
|
var bottomMargin = new BottomMarginBand { HeightF = 32 };
|
||||||
|
var reportHeader = new ReportHeaderBand { HeightF = 42 };
|
||||||
|
var pageHeader = new PageHeaderBand { HeightF = 28 };
|
||||||
|
var detail = new DetailBand { HeightF = 24 };
|
||||||
|
var reportFooter = new ReportFooterBand { HeightF = HasAnySummary(fields) ? 28 : 0 };
|
||||||
|
|
||||||
|
Bands.AddRange([topMargin, reportHeader, pageHeader, detail, reportFooter, bottomMargin]);
|
||||||
|
|
||||||
|
var pageWidth = PageWidth - Margins.Left - Margins.Right;
|
||||||
|
reportHeader.Controls.Add(new XRLabel
|
||||||
|
{
|
||||||
|
Text = GetLocalizedReportTitle(listForm, localizer),
|
||||||
|
BoundsF = new System.Drawing.RectangleF(0, 0, pageWidth, 30),
|
||||||
|
Font = new DXFont("Arial", 14F, DXFontStyle.Bold),
|
||||||
|
TextAlignment = TextAlignment.MiddleLeft
|
||||||
|
});
|
||||||
|
|
||||||
|
var generatedAt = new XRPageInfo
|
||||||
|
{
|
||||||
|
PageInfo = PageInfo.DateTime,
|
||||||
|
TextFormatString = "{0:dd.MM.yyyy HH:mm}",
|
||||||
|
BoundsF = new System.Drawing.RectangleF(0, 30, pageWidth / 2, 12),
|
||||||
|
Font = new DXFont("Arial", 7.5F),
|
||||||
|
TextAlignment = TextAlignment.MiddleLeft
|
||||||
|
};
|
||||||
|
reportHeader.Controls.Add(generatedAt);
|
||||||
|
|
||||||
|
var pageInfo = 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
|
||||||
|
};
|
||||||
|
bottomMargin.Controls.Add(pageInfo);
|
||||||
|
|
||||||
|
var widths = CalculateWidths(fields, pageWidth);
|
||||||
|
var headerTable = CreateTable(pageWidth, 28);
|
||||||
|
var headerRow = new XRTableRow();
|
||||||
|
var detailTable = CreateTable(pageWidth, 24);
|
||||||
|
var detailRow = new XRTableRow();
|
||||||
|
var footerTable = CreateTable(pageWidth, 24);
|
||||||
|
var footerRow = new XRTableRow();
|
||||||
|
|
||||||
|
for (var i = 0; i < fields.Count; i++)
|
||||||
|
{
|
||||||
|
var field = fields[i];
|
||||||
|
headerRow.Cells.Add(CreateHeaderCell(field, widths[i], localizer));
|
||||||
|
detailRow.Cells.Add(CreateDetailCell(field, widths[i]));
|
||||||
|
footerRow.Cells.Add(CreateFooterCell(field, widths[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
headerTable.Rows.Add(headerRow);
|
||||||
|
detailTable.Rows.Add(detailRow);
|
||||||
|
footerTable.Rows.Add(footerRow);
|
||||||
|
|
||||||
|
pageHeader.Controls.Add(headerTable);
|
||||||
|
detail.Controls.Add(detailTable);
|
||||||
|
if (HasAnySummary(fields))
|
||||||
|
{
|
||||||
|
reportFooter.Controls.Add(footerTable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XRTable CreateTable(float width, float height)
|
||||||
|
{
|
||||||
|
return new XRTable
|
||||||
|
{
|
||||||
|
BoundsF = new System.Drawing.RectangleF(0, 0, width, height),
|
||||||
|
Borders = BorderSide.All,
|
||||||
|
BorderWidth = 0.5F
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XRTableCell CreateHeaderCell(
|
||||||
|
ReportColumn column,
|
||||||
|
float width,
|
||||||
|
IStringLocalizer<PlatformResource> localizer)
|
||||||
|
{
|
||||||
|
var field = column.Field;
|
||||||
|
return new XRTableCell
|
||||||
|
{
|
||||||
|
Text = GetLocalizedFieldCaption(field, localizer),
|
||||||
|
WidthF = width,
|
||||||
|
Font = new DXFont("Arial", 8F, DXFontStyle.Bold),
|
||||||
|
BackColor = System.Drawing.Color.FromArgb(238, 242, 247),
|
||||||
|
Padding = new PaddingInfo(4, 4, 2, 2),
|
||||||
|
TextAlignment = TextAlignment.MiddleCenter,
|
||||||
|
Multiline = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetLocalizedReportTitle(ListForm listForm, IStringLocalizer<PlatformResource> localizer)
|
||||||
|
{
|
||||||
|
var title = string.IsNullOrWhiteSpace(listForm.Title) ? listForm.Name : listForm.Title;
|
||||||
|
return Localize(localizer, title, listForm.ListFormCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetLocalizedFieldCaption(ListFormField field, IStringLocalizer<PlatformResource> localizer)
|
||||||
|
{
|
||||||
|
var caption = string.IsNullOrWhiteSpace(field.CaptionName) ? field.FieldName : field.CaptionName;
|
||||||
|
return Localize(localizer, caption, field.FieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Localize(IStringLocalizer<PlatformResource> 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)
|
||||||
|
{
|
||||||
|
var field = column.Field;
|
||||||
|
var cell = new XRTableCell
|
||||||
|
{
|
||||||
|
WidthF = width,
|
||||||
|
Font = new DXFont("Arial", 8F),
|
||||||
|
Padding = new PaddingInfo(4, 4, 2, 2),
|
||||||
|
TextAlignment = ToTextAlignment(field.Alignment, field.SourceDbType),
|
||||||
|
Multiline = false,
|
||||||
|
CanGrow = false
|
||||||
|
};
|
||||||
|
cell.ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", $"[{column.BindingName}]"));
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XRTableCell CreateFooterCell(ReportColumn column, float width)
|
||||||
|
{
|
||||||
|
var field = column.Field;
|
||||||
|
var summaryType = GetSummaryType(field.TotalSummaryJson);
|
||||||
|
var cell = new XRTableCell
|
||||||
|
{
|
||||||
|
WidthF = width,
|
||||||
|
Font = new DXFont("Arial", 8F, DXFontStyle.Bold),
|
||||||
|
Padding = new PaddingInfo(4, 4, 2, 2),
|
||||||
|
TextAlignment = ToTextAlignment(field.Alignment, field.SourceDbType)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (summaryType == null)
|
||||||
|
{
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
cell.ExpressionBindings.Add(new ExpressionBinding("BeforePrint", "Text", $"sumSum([{column.BindingName}])"));
|
||||||
|
cell.Summary = new XRSummary
|
||||||
|
{
|
||||||
|
Running = SummaryRunning.Report,
|
||||||
|
Func = summaryType.Value,
|
||||||
|
IgnoreNullValues = true
|
||||||
|
};
|
||||||
|
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float[] CalculateWidths(IReadOnlyList<ListFormField> fields, float pageWidth)
|
||||||
|
{
|
||||||
|
var raw = fields.Select(x => (float)Math.Max(x.Width ?? 90, 60)).ToArray();
|
||||||
|
var total = raw.Sum();
|
||||||
|
if (total <= 0)
|
||||||
|
{
|
||||||
|
return fields.Select(_ => pageWidth / fields.Count).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw.Select(x => pageWidth * x / total).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float[] CalculateWidths(IReadOnlyList<ReportColumn> fields, float pageWidth)
|
||||||
|
{
|
||||||
|
var raw = fields.Select(x => (float)Math.Max(x.Field.Width ?? 90, 60)).ToArray();
|
||||||
|
var total = raw.Sum();
|
||||||
|
if (total <= 0)
|
||||||
|
{
|
||||||
|
return fields.Select(_ => pageWidth / fields.Count).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return raw.Select(x => pageWidth * x / total).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasAnySummary(IEnumerable<ReportColumn> fields)
|
||||||
|
{
|
||||||
|
return fields.Any(x => GetSummaryType(x.Field.TotalSummaryJson) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetReportFieldName(SelectField selectField)
|
||||||
|
{
|
||||||
|
if (selectField?.JoinOptions != null && !string.IsNullOrWhiteSpace(selectField.JoinOptions.FieldNameAlias))
|
||||||
|
{
|
||||||
|
return selectField.JoinOptions.FieldNameAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectField?.FieldName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record ReportColumn(ListFormField Field, string BindingName);
|
||||||
|
|
||||||
|
private static SummaryFunc? GetSummaryType(string totalSummaryJson)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(totalSummaryJson))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var value = totalSummaryJson.ToLowerInvariant();
|
||||||
|
if (value.Contains("\"summarytype\":\"sum\""))
|
||||||
|
{
|
||||||
|
return SummaryFunc.Sum;
|
||||||
|
}
|
||||||
|
if (value.Contains("\"summarytype\":\"avg\""))
|
||||||
|
{
|
||||||
|
return SummaryFunc.Avg;
|
||||||
|
}
|
||||||
|
if (value.Contains("\"summarytype\":\"min\""))
|
||||||
|
{
|
||||||
|
return SummaryFunc.Min;
|
||||||
|
}
|
||||||
|
if (value.Contains("\"summarytype\":\"max\""))
|
||||||
|
{
|
||||||
|
return SummaryFunc.Max;
|
||||||
|
}
|
||||||
|
if (value.Contains("\"summarytype\":\"count\""))
|
||||||
|
{
|
||||||
|
return SummaryFunc.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static 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<string, string> parameters, string name)
|
||||||
|
{
|
||||||
|
return parameters.TryGetValue(name, out var value) ? value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, StringValues> ToStringValues(IDictionary<string, string> parameters)
|
||||||
|
{
|
||||||
|
return parameters.ToDictionary(x => x.Key, x => new StringValues(x.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,7 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
|
||||||
|
|
||||||
// TemplateReport için ServiceProvider'ı set et
|
// TemplateReport için ServiceProvider'ı set et
|
||||||
Sozsoft.Reports.PredefinedReports.TemplateReport.SetServiceProvider(serviceProvider);
|
Sozsoft.Reports.PredefinedReports.TemplateReport.SetServiceProvider(serviceProvider);
|
||||||
|
Sozsoft.Reports.PredefinedReports.DynamicListFormReport.SetServiceProvider(serviceProvider);
|
||||||
|
|
||||||
if (!Directory.Exists(reportDirectory))
|
if (!Directory.Exists(reportDirectory))
|
||||||
{
|
{
|
||||||
|
|
@ -34,6 +35,7 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
|
||||||
|
|
||||||
// TemplateReport için ServiceProvider'ı set et
|
// TemplateReport için ServiceProvider'ı set et
|
||||||
Sozsoft.Reports.PredefinedReports.TemplateReport.SetServiceProvider(serviceProvider);
|
Sozsoft.Reports.PredefinedReports.TemplateReport.SetServiceProvider(serviceProvider);
|
||||||
|
Sozsoft.Reports.PredefinedReports.DynamicListFormReport.SetServiceProvider(serviceProvider);
|
||||||
|
|
||||||
if (!Directory.Exists(reportDirectory))
|
if (!Directory.Exists(reportDirectory))
|
||||||
{
|
{
|
||||||
|
|
@ -65,8 +67,8 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
|
||||||
// Implement your own logic to prohibit URLs that contain spaces or other specific characters.
|
// Implement your own logic to prohibit URLs that contain spaces or other specific characters.
|
||||||
// Return **true** if no validation is required.
|
// Return **true** if no validation is required.
|
||||||
|
|
||||||
// Query string varsa sadece rapor adını kontrol et
|
// Query string encoded gelse bile sadece rapor adını kontrol et.
|
||||||
string reportName = url.Contains("?") ? url.Split('?')[0] : url;
|
var (reportName, _) = ParseReportUrl(url);
|
||||||
return Path.GetFileName(reportName) == reportName;
|
return Path.GetFileName(reportName) == reportName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,26 +80,14 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
|
||||||
// if the parameters are included in the report URL's query string.
|
// if the parameters are included in the report URL's query string.
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// URL'den rapor adını ve parametreleri ayır
|
var (reportName, parameters) = ParseReportUrl(url);
|
||||||
string reportName = url;
|
|
||||||
Dictionary<string, string> parameters = new Dictionary<string, string>();
|
|
||||||
|
|
||||||
if (url.Contains("?"))
|
if (reportName == Sozsoft.Reports.PredefinedReports.DynamicListFormReport.ReportName)
|
||||||
{
|
{
|
||||||
var parts = url.Split('?');
|
var report = Sozsoft.Reports.PredefinedReports.DynamicListFormReport.Create(parameters);
|
||||||
reportName = parts[0];
|
using MemoryStream ms = new MemoryStream();
|
||||||
|
report.SaveLayoutToXml(ms);
|
||||||
// Query string parametrelerini parse et
|
return ms.ToArray();
|
||||||
var queryString = parts[1];
|
|
||||||
var paramPairs = queryString.Split('&');
|
|
||||||
foreach (var pair in paramPairs)
|
|
||||||
{
|
|
||||||
var keyValue = pair.Split('=');
|
|
||||||
if (keyValue.Length == 2)
|
|
||||||
{
|
|
||||||
parameters[keyValue[0]] = Uri.UnescapeDataString(keyValue[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Önce Reports klasöründen yüklemeyi dene
|
// Önce Reports klasöründen yüklemeyi dene
|
||||||
|
|
@ -177,6 +167,51 @@ public class CustomReportStorageWebExtension : DevExpress.XtraReports.Web.Extens
|
||||||
throw new FaultException(new FaultReason(string.Format("Could not find report '{0}'.", url)), new FaultCode("Server"), "GetData");
|
throw new FaultException(new FaultReason(string.Format("Could not find report '{0}'.", url)), new FaultCode("Server"), "GetData");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static (string ReportName, Dictionary<string, string> Parameters) ParseReportUrl(string url)
|
||||||
|
{
|
||||||
|
var decodedUrl = DecodeRepeatedly(url);
|
||||||
|
var reportName = decodedUrl;
|
||||||
|
var parameters = new Dictionary<string, string>();
|
||||||
|
|
||||||
|
var queryIndex = decodedUrl.IndexOf('?');
|
||||||
|
if (queryIndex < 0)
|
||||||
|
{
|
||||||
|
return (reportName, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
reportName = decodedUrl[..queryIndex];
|
||||||
|
var queryString = decodedUrl[(queryIndex + 1)..];
|
||||||
|
var paramPairs = queryString.Split('&', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
foreach (var pair in paramPairs)
|
||||||
|
{
|
||||||
|
var keyValue = pair.Split('=', 2);
|
||||||
|
if (keyValue.Length == 2 && !string.IsNullOrWhiteSpace(keyValue[0]))
|
||||||
|
{
|
||||||
|
parameters[keyValue[0]] = keyValue[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (reportName, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string DecodeRepeatedly(string value)
|
||||||
|
{
|
||||||
|
var decoded = value;
|
||||||
|
for (var i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
var next = Uri.UnescapeDataString(decoded);
|
||||||
|
if (next == decoded)
|
||||||
|
{
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
decoded = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
public override Dictionary<string, string> GetUrls()
|
public override Dictionary<string, string> GetUrls()
|
||||||
{
|
{
|
||||||
// Returns a dictionary that contains the report names (URLs) and display names.
|
// Returns a dictionary that contains the report names (URLs) and display names.
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,8 @@ export const ROUTES_ENUM = {
|
||||||
dynamicServicesEdit: '/admin/developerkit/dynamic-services/edit/:id',
|
dynamicServicesEdit: '/admin/developerkit/dynamic-services/edit/:id',
|
||||||
},
|
},
|
||||||
reports: {
|
reports: {
|
||||||
generator: '/admin/reports/generator',
|
view: '/admin/reports/:id/view',
|
||||||
view: '/admin/reports/:id',
|
design: '/admin/reports/:id/design',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
admin: {
|
admin: {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import React, { useMemo, lazy, Suspense, useEffect } from 'react'
|
import React, { useMemo, useEffect, useState } from 'react'
|
||||||
import { Container } from '@/components/shared'
|
import { Container } from '@/components/shared'
|
||||||
import { Helmet } from 'react-helmet'
|
import { Helmet } from 'react-helmet'
|
||||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||||
import { useParams, useLocation } from 'react-router-dom'
|
import { useParams, useLocation } from 'react-router-dom'
|
||||||
import { RequestOptions } from 'devexpress-reporting-react/dx-report-viewer'
|
import ReportViewer, {
|
||||||
|
RequestOptions,
|
||||||
|
} from 'devexpress-reporting-react/dx-report-viewer'
|
||||||
|
import { ajaxSetup } from '@devexpress/analytics-core/analytics-utils-native'
|
||||||
import { APP_NAME } from '@/constants/app.constant'
|
import { APP_NAME } from '@/constants/app.constant'
|
||||||
|
import { useStoreState } from '@/store'
|
||||||
// Lazy load
|
|
||||||
const ReportViewer = lazy(() =>
|
|
||||||
import('devexpress-reporting-react/dx-report-viewer')
|
|
||||||
)
|
|
||||||
|
|
||||||
const loadViewerCss = () => {
|
const loadViewerCss = () => {
|
||||||
const styles = [
|
const styles = [
|
||||||
|
|
@ -31,11 +30,33 @@ const DevexpressReportViewer: React.FC = () => {
|
||||||
const { translate } = useLocalization()
|
const { translate } = useLocalization()
|
||||||
const { id } = useParams<{ id: string }>()
|
const { id } = useParams<{ id: string }>()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const token = useStoreState((state) => state.auth.session.token)
|
||||||
|
const [isRequestAuthReady, setIsRequestAuthReady] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadViewerCss()
|
loadViewerCss()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsRequestAuthReady(false)
|
||||||
|
const authorization = token ? `Bearer ${token}` : undefined
|
||||||
|
|
||||||
|
const ajaxHeaders = ajaxSetup.ajaxSettings.headers ?? {}
|
||||||
|
|
||||||
|
if (authorization) {
|
||||||
|
ajaxSetup.ajaxSettings.headers = {
|
||||||
|
...ajaxHeaders,
|
||||||
|
Authorization: authorization,
|
||||||
|
}
|
||||||
|
setIsRequestAuthReady(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { Authorization: _ajaxAuthorization, ...nextAjaxHeaders } = ajaxHeaders
|
||||||
|
ajaxSetup.ajaxSettings.headers = nextAjaxHeaders
|
||||||
|
setIsRequestAuthReady(true)
|
||||||
|
}, [token])
|
||||||
|
|
||||||
const reportUrlWithParams = useMemo(() => {
|
const reportUrlWithParams = useMemo(() => {
|
||||||
if (!id) return ''
|
if (!id) return ''
|
||||||
return location.search ? `${id}${location.search}` : id
|
return location.search ? `${id}${location.search}` : id
|
||||||
|
|
@ -51,14 +72,14 @@ const DevexpressReportViewer: React.FC = () => {
|
||||||
defaultTitle={APP_NAME}
|
defaultTitle={APP_NAME}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Suspense fallback={<div>Rapor yükleniyor...</div>}>
|
{isRequestAuthReady && (
|
||||||
<ReportViewer reportUrl={reportUrlWithParams}>
|
<ReportViewer reportUrl={reportUrlWithParams}>
|
||||||
<RequestOptions
|
<RequestOptions
|
||||||
host={`${import.meta.env.VITE_API_URL}/`}
|
host={import.meta.env.VITE_API_URL}
|
||||||
invokeAction="DXXRDV"
|
invokeAction="/DXXRDV"
|
||||||
/>
|
/>
|
||||||
</ReportViewer>
|
</ReportViewer>
|
||||||
</Suspense>
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue