diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs index 7954a87..b7873a1 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicFormReport.cs @@ -686,15 +686,16 @@ public class DynamicFormReport : XtraReport { var bindingName = DynamicReportImageHelper.GetReportFieldName(selectField); var lookup = DynamicReportImageHelper.GetLookup(field); + var isMultiValue = IsMultiValueLookupEditor(editorType); if (lookup == null || selectField?.JoinOptions != null) { - return new FormReportField(field, bindingName, editorType, columnSpan, null, null, DynamicReportImageHelper.IsImageEditor(editorType)); + return new FormReportField(field, bindingName, editorType, columnSpan, null, null, isMultiValue, DynamicReportImageHelper.IsImageEditor(editorType)); } if (DynamicReportSqlBuilder.IsReportQueryableLookup(lookup)) { - return new FormReportField(field, DynamicReportSqlBuilder.GetLookupBindingName(field), editorType, columnSpan, lookup, null, false); + return new FormReportField(field, DynamicReportSqlBuilder.GetLookupBindingName(field), editorType, columnSpan, lookup, null, isMultiValue, false); } if (lookup.DataSourceType == UiLookupDataSourceTypeEnum.StaticData) @@ -707,10 +708,16 @@ public class DynamicFormReport : XtraReport columnSpan, lookup, staticLookupValues, + isMultiValue, false); } - return new FormReportField(field, bindingName, editorType, columnSpan, null, null, DynamicReportImageHelper.IsImageEditor(editorType)); + return new FormReportField(field, bindingName, editorType, columnSpan, null, null, isMultiValue, DynamicReportImageHelper.IsImageEditor(editorType)); + } + + private static bool IsMultiValueLookupEditor(string editorType) + { + return editorType is PlatformConsts.EditorTypes.dxTagBox or "dxGridBox"; } private static string CreateKeyFilter(string keyFieldName, string id) @@ -749,5 +756,6 @@ public class DynamicFormReport : XtraReport int ColumnSpan, LookupDto Lookup, Dictionary StaticLookupValues, + bool IsMultiValue, bool IsImage) : DynamicReportSqlBuilder.IReportLookupColumn; } diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicGridReport.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicGridReport.cs index 26174fc..a6213b8 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicGridReport.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicGridReport.cs @@ -567,15 +567,16 @@ public class DynamicGridReport : XtraReport { var bindingName = DynamicReportImageHelper.GetReportFieldName(selectField); var lookup = DynamicReportImageHelper.GetLookup(field); + var isMultiValue = IsMultiValueLookupEditor(editorType); if (lookup == null || selectField?.JoinOptions != null) { - return new ReportColumn(field, bindingName, null, null, isImage, isHtml, editorType); + return new ReportColumn(field, bindingName, null, null, isMultiValue, isImage, isHtml, editorType); } if (DynamicReportSqlBuilder.IsReportQueryableLookup(lookup)) { - return new ReportColumn(field, DynamicReportSqlBuilder.GetLookupBindingName(field), lookup, null, isImage, isHtml, editorType); + return new ReportColumn(field, DynamicReportSqlBuilder.GetLookupBindingName(field), lookup, null, isMultiValue, isImage, isHtml, editorType); } if (lookup.DataSourceType == UiLookupDataSourceTypeEnum.StaticData) @@ -586,12 +587,18 @@ public class DynamicGridReport : XtraReport staticLookupValues?.Count > 0 ? DynamicReportSqlBuilder.GetLookupBindingName(field) : bindingName, lookup, staticLookupValues, + isMultiValue, isImage, isHtml, editorType); } - return new ReportColumn(field, bindingName, null, null, isImage, isHtml, editorType); + return new ReportColumn(field, bindingName, null, null, isMultiValue, isImage, isHtml, editorType); + } + + private static bool IsMultiValueLookupEditor(string editorType) + { + return editorType is PlatformConsts.EditorTypes.dxTagBox or "dxGridBox"; } private sealed record ReportColumn( @@ -599,6 +606,7 @@ public class DynamicGridReport : XtraReport string BindingName, LookupDto Lookup, Dictionary StaticLookupValues, + bool IsMultiValue, bool IsImage, bool IsHtml, string EditorType, diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportSqlBuilder.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportSqlBuilder.cs index 2785006..6a4656f 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportSqlBuilder.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicReportSqlBuilder.cs @@ -25,6 +25,7 @@ internal static class DynamicReportSqlBuilder string BindingName { get; } LookupDto Lookup { get; } Dictionary StaticLookupValues { get; } + bool IsMultiValue { get; } } internal static string GetLookupBindingName(ListFormField field) @@ -92,6 +93,11 @@ internal static class DynamicReportSqlBuilder IReportLookupColumn column, DataSourceTypeEnum dataSourceType) { + if (column.IsMultiValue) + { + return CreateStaticMultiLookupSelectPart(column, dataSourceType); + } + var sourceField = GetSourceField(column.Field.FieldName); var cases = column.StaticLookupValues .Select(item => $"WHEN {DynamicReportImageHelper.ToSqlLiteral(item.Key, dataSourceType)} THEN {DynamicReportImageHelper.ToSqlLiteral(item.Value, dataSourceType)}"); @@ -110,6 +116,11 @@ internal static class DynamicReportSqlBuilder return null; } + if (column.IsMultiValue) + { + return CreateQueryMultiLookupSelectPart(column, lookupQuery, dataSourceType); + } + var lookupAlias = QuoteSqlIdentifier(LookupAlias); var valueExpr = QuoteSqlIdentifier(column.Lookup.ValueExpr ?? "Key"); var displayExpr = QuoteSqlIdentifier(column.Lookup.DisplayExpr ?? "Name"); @@ -123,6 +134,65 @@ internal static class DynamicReportSqlBuilder return $"({scalarQuery}) AS {resultAlias}"; } + private static string CreateStaticMultiLookupSelectPart( + IReportLookupColumn column, + DataSourceTypeEnum dataSourceType) + { + var sourceField = GetSourceField(column.Field.FieldName); + var resultAlias = QuoteSqlIdentifier(column.BindingName); + var values = string.Join( + ", ", + column.StaticLookupValues.Select(item => + $"({DynamicReportImageHelper.ToSqlLiteral(item.Key, dataSourceType)}, {DynamicReportImageHelper.ToSqlLiteral(item.Value, dataSourceType)})")); + + if (dataSourceType == DataSourceTypeEnum.Mssql) + { + return $""" + (SELECT STRING_AGG(CAST({LookupAliasForValues()}.DisplayValue AS NVARCHAR(MAX)), N', ') + FROM (VALUES {values}) AS {LookupAliasForValues()}(ValueKey, DisplayValue) + INNER JOIN STRING_SPLIT(CAST({sourceField} AS NVARCHAR(MAX)), N'{PlatformConsts.MultiValueDelimiter}') AS SplitValues + ON CAST({LookupAliasForValues()}.ValueKey AS NVARCHAR(4000)) = LTRIM(RTRIM(SplitValues.value))) AS {resultAlias} + """.ReplaceLineEndings(" ").Trim(); + } + + return $""" + (SELECT string_agg(CAST({LookupAliasForValues()}."DisplayValue" AS TEXT), ', ') + FROM (VALUES {values}) AS {LookupAliasForValues()}("ValueKey", "DisplayValue") + INNER JOIN unnest(string_to_array(CAST({sourceField} AS TEXT), '{PlatformConsts.MultiValueDelimiter}')) AS SplitValues(value) + ON CAST({LookupAliasForValues()}."ValueKey" AS TEXT) = btrim(SplitValues.value)) AS {resultAlias} + """.ReplaceLineEndings(" ").Trim(); + } + + private static string CreateQueryMultiLookupSelectPart( + IReportLookupColumn column, + string lookupQuery, + DataSourceTypeEnum dataSourceType) + { + var lookupAlias = QuoteSqlIdentifier(LookupAlias); + var valueExpr = QuoteSqlIdentifier(column.Lookup.ValueExpr ?? "Key"); + var displayExpr = QuoteSqlIdentifier(column.Lookup.DisplayExpr ?? "Name"); + var sourceField = GetSourceField(column.Field.FieldName); + var resultAlias = QuoteSqlIdentifier(column.BindingName); + + var scalarQuery = dataSourceType == DataSourceTypeEnum.Mssql + ? $""" + SELECT STRING_AGG(CAST({lookupAlias}.{displayExpr} AS NVARCHAR(MAX)), N', ') + FROM ({lookupQuery}) AS {lookupAlias} + INNER JOIN STRING_SPLIT(CAST({sourceField} AS NVARCHAR(MAX)), N'{PlatformConsts.MultiValueDelimiter}') AS SplitValues + ON CAST({lookupAlias}.{valueExpr} AS NVARCHAR(4000)) = LTRIM(RTRIM(SplitValues.value)) + """.ReplaceLineEndings(" ").Trim() + : $""" + SELECT string_agg(CAST({lookupAlias}.{displayExpr} AS TEXT), ', ') + FROM ({lookupQuery}) AS {lookupAlias} + INNER JOIN unnest(string_to_array(CAST({sourceField} AS TEXT), '{PlatformConsts.MultiValueDelimiter}')) AS SplitValues(value) + ON CAST({lookupAlias}.{valueExpr} AS TEXT) = btrim(SplitValues.value) + """.ReplaceLineEndings(" ").Trim(); + + return $"({scalarQuery}) AS {resultAlias}"; + } + + private static string LookupAliasForValues() => "LookupValues"; + private static string GetSourceField(string fieldName) { return $"{QuoteSqlIdentifier(SourceAlias)}.{QuoteSqlIdentifier(fieldName)}"; diff --git a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicTreeReport.cs b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicTreeReport.cs index d76fd68..7ac7ab5 100644 --- a/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicTreeReport.cs +++ b/api/src/Sozsoft.Platform.HttpApi.Host/PredefinedReports/DynamicTreeReport.cs @@ -715,15 +715,16 @@ public class DynamicTreeReport : XtraReport { var bindingName = DynamicReportImageHelper.GetReportFieldName(selectField); var lookup = DynamicReportImageHelper.GetLookup(field); + var isMultiValue = IsMultiValueLookupEditor(editorType); if (lookup == null || selectField?.JoinOptions != null) { - return new ReportColumn(field, bindingName, null, null, isImage, isHtml, editorType); + return new ReportColumn(field, bindingName, null, null, isMultiValue, isImage, isHtml, editorType); } if (DynamicReportSqlBuilder.IsReportQueryableLookup(lookup)) { - return new ReportColumn(field, DynamicReportSqlBuilder.GetLookupBindingName(field), lookup, null, isImage, isHtml, editorType); + return new ReportColumn(field, DynamicReportSqlBuilder.GetLookupBindingName(field), lookup, null, isMultiValue, isImage, isHtml, editorType); } if (lookup.DataSourceType == UiLookupDataSourceTypeEnum.StaticData) @@ -734,12 +735,18 @@ public class DynamicTreeReport : XtraReport staticLookupValues?.Count > 0 ? DynamicReportSqlBuilder.GetLookupBindingName(field) : bindingName, lookup, staticLookupValues, + isMultiValue, isImage, isHtml, editorType); } - return new ReportColumn(field, bindingName, null, null, isImage, isHtml, editorType); + return new ReportColumn(field, bindingName, null, null, isMultiValue, isImage, isHtml, editorType); + } + + private static bool IsMultiValueLookupEditor(string editorType) + { + return editorType is PlatformConsts.EditorTypes.dxTagBox or "dxGridBox"; } private static TreeOptionDto GetTreeOptions(ListForm listForm) @@ -767,6 +774,7 @@ public class DynamicTreeReport : XtraReport string BindingName, LookupDto Lookup, Dictionary StaticLookupValues, + bool IsMultiValue, bool IsImage, bool IsHtml, string EditorType,