From 63f95dab275413e9460d3cefe3f1d81f0de6a20e Mon Sep 17 00:00:00 2001 From: Ivar Nesje Date: Wed, 22 Feb 2023 08:22:20 +0100 Subject: [PATCH] Add new DynamicsPdfFormatter (#173) --- .../Controllers/PdfController.cs | 27 ++++----- src/Altinn.App.Core/Features/IPdfFormatter.cs | 29 ++++++--- .../Features/Pdf/DynamicsPdfFormatter.cs | 60 +++++++++++++++++++ .../Internal/Pdf/PdfService.cs | 8 +-- 4 files changed, 99 insertions(+), 25 deletions(-) create mode 100644 src/Altinn.App.Core/Features/Pdf/DynamicsPdfFormatter.cs diff --git a/src/Altinn.App.Api/Controllers/PdfController.cs b/src/Altinn.App.Api/Controllers/PdfController.cs index 02f9bb2c9..bc9bb481a 100644 --- a/src/Altinn.App.Api/Controllers/PdfController.cs +++ b/src/Altinn.App.Api/Controllers/PdfController.cs @@ -1,7 +1,6 @@ -using System; -using System.Linq; +#nullable enable + using System.Text.Json; -using System.Threading.Tasks; using Altinn.App.Core.Features; using Altinn.App.Core.Interface; using Altinn.App.Core.Internal.AppModel; @@ -66,13 +65,13 @@ public async Task GetPdfFormat( return NotFound("Did not find instance"); } - string taskId = instance.Process?.CurrentTask?.ElementId; + string? taskId = instance.Process?.CurrentTask?.ElementId; if (taskId == null) { return Conflict("Instance does not have a valid currentTask"); } - DataElement dataElement = instance.Data.FirstOrDefault(d => d.Id == dataGuid.ToString()); + DataElement? dataElement = instance.Data.FirstOrDefault(d => d.Id == dataGuid.ToString()); if (dataElement == null) { return NotFound("Did not find data element"); @@ -84,20 +83,20 @@ public async Task GetPdfFormat( JsonSerializerOptions options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; string layoutSetsString = _resources.GetLayoutSets(); - LayoutSets layoutSets = null; - LayoutSet layoutSet = null; + LayoutSets? layoutSets = null; + LayoutSet? layoutSet = null; if (!string.IsNullOrEmpty(layoutSetsString)) { - layoutSets = JsonSerializer.Deserialize(layoutSetsString, options); + layoutSets = JsonSerializer.Deserialize(layoutSetsString, options)!; layoutSet = layoutSets.Sets?.FirstOrDefault(t => t.DataType.Equals(dataElement.DataType) && t.Tasks.Contains(taskId)); } - string layoutSettingsFileContent = layoutSet == null ? _resources.GetLayoutSettingsString() : _resources.GetLayoutSettingsStringForSet(layoutSet.Id); + string? layoutSettingsFileContent = layoutSet == null ? _resources.GetLayoutSettingsString() : _resources.GetLayoutSettingsStringForSet(layoutSet.Id); - LayoutSettings layoutSettings = null; + LayoutSettings? layoutSettings = null; if (!string.IsNullOrEmpty(layoutSettingsFileContent)) { - layoutSettings = JsonSerializer.Deserialize(layoutSettingsFileContent, options); + layoutSettings = JsonSerializer.Deserialize(layoutSettingsFileContent, options)!; } // Ensure layoutsettings are initialized in FormatPdf @@ -109,12 +108,12 @@ public async Task GetPdfFormat( object data = await _dataClient.GetFormData(instanceGuid, dataType, org, app, instanceOwnerPartyId, new Guid(dataElement.Id)); - layoutSettings = await _pdfFormatter.FormatPdf(layoutSettings, data); + layoutSettings = await _pdfFormatter.FormatPdf(layoutSettings, data, instance, layoutSet); var result = new { - ExcludedPages = layoutSettings.Pages.ExcludeFromPdf, - ExcludedComponents = layoutSettings.Components.ExcludeFromPdf, + ExcludedPages = layoutSettings?.Pages?.ExcludeFromPdf ?? new List(), + ExcludedComponents = layoutSettings?.Components?.ExcludeFromPdf ?? new List(), }; return Ok(result); } diff --git a/src/Altinn.App.Core/Features/IPdfFormatter.cs b/src/Altinn.App.Core/Features/IPdfFormatter.cs index f2d031c5a..b45a6f7c0 100644 --- a/src/Altinn.App.Core/Features/IPdfFormatter.cs +++ b/src/Altinn.App.Core/Features/IPdfFormatter.cs @@ -1,15 +1,30 @@ using Altinn.App.Core.Models; +using Altinn.Platform.Storage.Interface.Models; -namespace Altinn.App.Core.Features +namespace Altinn.App.Core.Features; + +/// +/// Interface to customize PDF formatting. +/// +/// +/// This interface has been changed and both methods now has default implementation for backwards compatibility. +/// All users will call the method with the Instance parameter, and a user only needs to implement one +/// +public interface IPdfFormatter { /// - /// Interface to customize PDF formatting. + /// Old method to format the PDF dynamically (without Instance) + /// + Task FormatPdf(LayoutSettings layoutSettings, object data) + { + throw new NotImplementedException(); + } + + /// + /// Method to format the PDF dynamically (new version with the instance) /// - public interface IPdfFormatter + async Task FormatPdf(LayoutSettings layoutSettings, object data, Instance instance, LayoutSet? layoutSet) { - /// - /// Method to format the PDF dynamically - /// - Task FormatPdf(LayoutSettings layoutSettings, object data); + return await FormatPdf(layoutSettings, data); } } diff --git a/src/Altinn.App.Core/Features/Pdf/DynamicsPdfFormatter.cs b/src/Altinn.App.Core/Features/Pdf/DynamicsPdfFormatter.cs new file mode 100644 index 000000000..ff4cbc69e --- /dev/null +++ b/src/Altinn.App.Core/Features/Pdf/DynamicsPdfFormatter.cs @@ -0,0 +1,60 @@ +using Altinn.App.Core.Internal.Expressions; +using Altinn.App.Core.Models; +using Altinn.Platform.Storage.Interface.Models; + +namespace Altinn.App.Core.Features.Pdf; + +/// +/// Custom formatter that reads the `hidden` properties of components and pages +/// to determine if they should be hidden in PDF as well. +/// +/// +/// Add this to dependency injection in order to run dynamics for hiding pages and comonents. +/// +/// services.AddTrancient<IPdfFormatter, DynamicsPdfFormatter>(); +/// +/// +public class DynamicsPdfFormatter : IPdfFormatter +{ + private readonly LayoutEvaluatorStateInitializer _layoutStateInit; + + /// + /// Constructor for + /// + public DynamicsPdfFormatter(LayoutEvaluatorStateInitializer layoutStateInit) + { + _layoutStateInit = layoutStateInit; + } + + /// + public async Task FormatPdf(LayoutSettings layoutSettings, object data, Instance instance, LayoutSet? layoutSet) + { + layoutSettings.Pages ??= new(); + layoutSettings.Pages.ExcludeFromPdf ??= new(); + layoutSettings.Components ??= new(); + layoutSettings.Components.ExcludeFromPdf ??= new(); + + var state = await _layoutStateInit.Init(instance, data, layoutSetId: layoutSet?.Id); + foreach (var pageContext in state.GetComponentContexts()) + { + var pageHidden = ExpressionEvaluator.EvaluateBooleanExpression(state, pageContext, "hidden", false); + if (pageHidden) + { + layoutSettings.Pages.ExcludeFromPdf.Add(pageContext.Component.Id); + } + else + { + //TODO: figure out how pdf reacts to groups one level down. + foreach (var componentContext in pageContext.ChildContexts) + { + var componentHidden = ExpressionEvaluator.EvaluateBooleanExpression(state, componentContext, "hidden", false); + if (componentHidden) + { + layoutSettings.Components.ExcludeFromPdf.Add(componentContext.Component.Id); + } + } + } + } + return layoutSettings; + } +} \ No newline at end of file diff --git a/src/Altinn.App.Core/Internal/Pdf/PdfService.cs b/src/Altinn.App.Core/Internal/Pdf/PdfService.cs index 426c71267..b1f99bc63 100644 --- a/src/Altinn.App.Core/Internal/Pdf/PdfService.cs +++ b/src/Altinn.App.Core/Internal/Pdf/PdfService.cs @@ -72,7 +72,7 @@ IOptions generalSettings _dataClient = dataClient; _httpContextAccessor = httpContextAccessor; _profileClient = profileClient; - _registerClient= registerClient; + _registerClient = registerClient; _pdfFormatter = pdfFormatter; _pdfGeneratorClient = pdfGeneratorClient; _pdfGeneratorSettings = pdfGeneratorSettings.Value; @@ -140,7 +140,7 @@ public async Task GenerateAndStoreReceiptPDF(Instance instance, string taskId, D object data = await _dataClient.GetFormData(instanceGuid, dataElementModelType, org, app, instanceOwnerId, new Guid(dataElement.Id)); - layoutSettings = await _pdfFormatter.FormatPdf(layoutSettings, data); + layoutSettings = await _pdfFormatter.FormatPdf(layoutSettings, data, instance, layoutSet); XmlSerializer serializer = new XmlSerializer(dataElementModelType); using MemoryStream stream = new MemoryStream(); @@ -216,7 +216,7 @@ private async Task StorePDF(Stream pdfStream, Instance instance, Te textResource.Resources.Find(textResourceElement => textResourceElement.Id.Equals("ServiceName")); fileName = (titleText != null && !string.IsNullOrEmpty(titleText.Value)) ? $"{titleText.Value}.pdf" : $"{app}.pdf"; - + fileName = GetValidFileName(fileName); return await _dataClient.InsertBinaryData( @@ -280,7 +280,7 @@ private static string GetFileName(Instance instance, TextResource textResource) { fileName = titleText.Value + ".pdf"; } - + return GetValidFileName(fileName); }