Compare commits
3 Commits
bugfix/leg
...
bugfix/rep
| Author | SHA1 | Date | |
|---|---|---|---|
| babd96200a | |||
| f9cfcf1f19 | |||
| ffb9e07466 |
@@ -1,6 +1,7 @@
|
|||||||
Imports System.IO
|
Imports System.IO
|
||||||
Imports DigitalData.Modules.Database
|
Imports DigitalData.Modules.Database
|
||||||
Imports DigitalData.Modules.Logging
|
Imports DigitalData.Modules.Logging
|
||||||
|
Imports EnvelopeGenerator.CommonServices
|
||||||
Imports EnvelopeGenerator.CommonServices.Jobs
|
Imports EnvelopeGenerator.CommonServices.Jobs
|
||||||
Imports EnvelopeGenerator.CommonServices.Jobs.FinalizeDocument
|
Imports EnvelopeGenerator.CommonServices.Jobs.FinalizeDocument
|
||||||
Imports GdPicture14
|
Imports GdPicture14
|
||||||
@@ -145,64 +146,91 @@ Public Class frmFinalizePDF
|
|||||||
log.AppendLine($"Document bytes: {oBuffer.Length}")
|
log.AppendLine($"Document bytes: {oBuffer.Length}")
|
||||||
|
|
||||||
' 3. Check what BurnAnnotsToPDF will do internally
|
' 3. Check what BurnAnnotsToPDF will do internally
|
||||||
|
Dim oEnvelopeEntity As Envelope = Nothing
|
||||||
Using scope = Factory.Shared.ScopeFactory.CreateScope()
|
Using scope = Factory.Shared.ScopeFactory.CreateScope()
|
||||||
Dim envRepo = scope.ServiceProvider.Repository(Of Envelope)()
|
Dim envRepo = scope.ServiceProvider.Repository(Of Envelope)()
|
||||||
Dim envelope = envRepo.Where(Function(env) env.Id = envelopeId).FirstOrDefault()
|
oEnvelopeEntity = envRepo.Where(Function(env) env.Id = envelopeId).FirstOrDefault()
|
||||||
|
|
||||||
If envelope Is Nothing Then
|
If oEnvelopeEntity Is Nothing Then
|
||||||
log.AppendLine("ERROR: Envelope not found in EF Core!")
|
log.AppendLine("ERROR: Envelope not found in EF Core!")
|
||||||
txtResult.Text = log.ToString()
|
txtResult.Text = log.ToString()
|
||||||
Return
|
Return
|
||||||
End If
|
End If
|
||||||
|
|
||||||
log.AppendLine($"Envelope found: Id={envelope.Id}, EnvelopeTypeId={envelope.EnvelopeTypeId}")
|
log.AppendLine($"Envelope found: Id={oEnvelopeEntity.Id}, EnvelopeTypeId={oEnvelopeEntity.EnvelopeTypeId}")
|
||||||
log.AppendLine($"ReadOnly (IsReadAndConfirm): {envelope.ReadOnly}")
|
log.AppendLine($"ReadOnly (IsReadAndConfirm): {oEnvelopeEntity.ReadOnly}")
|
||||||
|
|
||||||
If envelope.ReadOnly Then
|
If oEnvelopeEntity.ReadOnly Then
|
||||||
log.AppendLine(">>> EARLY RETURN: ReadOnly=True, original PDF returned without burning")
|
log.AppendLine(">>> ReadOnly=True: original PDF will be used without burning (annotations skipped)")
|
||||||
txtResult.Text = log.ToString()
|
|
||||||
Return
|
|
||||||
End If
|
|
||||||
|
|
||||||
Dim sigRepo = scope.ServiceProvider.Repository(Of DocReceiverElement)()
|
|
||||||
Dim elements = sigRepo _
|
|
||||||
.Where(Function(sig) sig.Document.EnvelopeId = envelopeId) _
|
|
||||||
.Include(Function(sig) sig.Annotations) _
|
|
||||||
.ToList()
|
|
||||||
|
|
||||||
log.AppendLine($"Elements (Signature) count: {elements.Count}")
|
|
||||||
|
|
||||||
If elements.Any() Then
|
|
||||||
log.AppendLine(">>> PATH: BurnElementAnnotsToPDF (new element-based path)")
|
|
||||||
For Each elem In elements
|
|
||||||
Dim annotCount = If(elem.Annotations IsNot Nothing, elem.Annotations.Count(), 0)
|
|
||||||
log.AppendLine($" Element Id={elem.Id}, Page={elem.Page}, X={elem.X}, Y={elem.Y}, W={elem.Width}, H={elem.Height}, Annotations={annotCount}")
|
|
||||||
If elem.Annotations IsNot Nothing Then
|
|
||||||
For Each annot In elem.Annotations
|
|
||||||
log.AppendLine($" Annot: Name={annot.Name}, Type={annot.Type}, X={annot.X}, Y={annot.Y}, W={annot.Width}, H={annot.Height}")
|
|
||||||
Next
|
|
||||||
End If
|
|
||||||
Next
|
|
||||||
Else
|
Else
|
||||||
log.AppendLine(">>> PATH: BurnInstantJSONAnnotsToPDF (old JSON-based path)")
|
Dim sigRepo = scope.ServiceProvider.Repository(Of DocReceiverElement)()
|
||||||
|
Dim elements = sigRepo _
|
||||||
|
.Where(Function(sig) sig.Document.EnvelopeId = envelopeId) _
|
||||||
|
.Include(Function(sig) sig.Annotations) _
|
||||||
|
.ToList()
|
||||||
|
|
||||||
|
log.AppendLine($"Elements (Signature) count: {elements.Count}")
|
||||||
|
|
||||||
|
If elements.Any() Then
|
||||||
|
log.AppendLine(">>> PATH: BurnElementAnnotsToPDF (new element-based path)")
|
||||||
|
For Each elem In elements
|
||||||
|
Dim annotCount = If(elem.Annotations IsNot Nothing, elem.Annotations.Count(), 0)
|
||||||
|
log.AppendLine($" Element Id={elem.Id}, Page={elem.Page}, X={elem.X}, Y={elem.Y}, W={elem.Width}, H={elem.Height}, Annotations={annotCount}")
|
||||||
|
If elem.Annotations IsNot Nothing Then
|
||||||
|
For Each annot In elem.Annotations
|
||||||
|
log.AppendLine($" Annot: Name={annot.Name}, Type={annot.Type}, X={annot.X}, Y={annot.Y}, W={annot.Width}, H={annot.Height}")
|
||||||
|
Next
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
Else
|
||||||
|
log.AppendLine(">>> PATH: BurnInstantJSONAnnotsToPDF (old JSON-based path)")
|
||||||
|
End If
|
||||||
End If
|
End If
|
||||||
End Using
|
End Using
|
||||||
|
|
||||||
' 4. Actually call BurnAnnotsToPDF (same as Service)
|
' 4. Burn annotations (ReadOnly envelopes return original PDF unchanged)
|
||||||
log.AppendLine("")
|
log.AppendLine("")
|
||||||
log.AppendLine("Calling BurnAnnotsToPDF...")
|
log.AppendLine("Calling BurnAnnotsToPDF...")
|
||||||
Dim oNewBuffer = PDFBurner.BurnAnnotsToPDF(oBuffer, oJsonList, envelopeId)
|
Dim oBurnedBuffer As Byte() = PDFBurner.BurnAnnotsToPDF(oBuffer, oJsonList, envelopeId)
|
||||||
log.AppendLine($"Result bytes: {oNewBuffer.Length}")
|
log.AppendLine($"Burned bytes: {oBurnedBuffer.Length}")
|
||||||
log.AppendLine($"Same as input: {oBuffer.Length = oNewBuffer.Length AndAlso oBuffer.SequenceEqual(oNewBuffer)}")
|
log.AppendLine($"Unchanged (ReadOnly): {oBuffer.Length = oBurnedBuffer.Length AndAlso oBuffer.SequenceEqual(oBurnedBuffer)}")
|
||||||
|
|
||||||
' 5. Write output
|
' 5. Build State and load Envelope domain model for ReportCreator
|
||||||
|
log.AppendLine("")
|
||||||
|
log.AppendLine("Loading envelope domain model for report...")
|
||||||
|
Dim oState As New State() With {
|
||||||
|
.Database = Database,
|
||||||
|
.LogConfig = LogConfig
|
||||||
|
}
|
||||||
|
Dim oEnvelopeModel As New EnvelopeModel(oState)
|
||||||
|
Dim oEnvelope = oEnvelopeModel.GetById(envelopeId)
|
||||||
|
If oEnvelope Is Nothing Then
|
||||||
|
log.AppendLine("ERROR: Envelope not found via EnvelopeModel!")
|
||||||
|
txtResult.Text = log.ToString()
|
||||||
|
Return
|
||||||
|
End If
|
||||||
|
log.AppendLine($"Envelope loaded: {oEnvelope.Id}")
|
||||||
|
|
||||||
|
' 6. Create signing certificate report
|
||||||
|
log.AppendLine("Creating signing certificate report...")
|
||||||
|
Dim oReportCreator As New ReportCreator(LogConfig, oState)
|
||||||
|
Dim oReportBuffer As Byte() = oReportCreator.CreateReport(oEnvelope)
|
||||||
|
log.AppendLine($"Report bytes: {oReportBuffer.Length}")
|
||||||
|
|
||||||
|
' 7. Merge burned document + report (last page)
|
||||||
|
log.AppendLine("Merging documents...")
|
||||||
|
Dim oMerger As New PDFMerger(LogConfig, pGDPictureLicenseKey)
|
||||||
|
Dim oMergedBuffer As Byte() = oMerger.MergeDocuments(oBurnedBuffer, oReportBuffer)
|
||||||
|
log.AppendLine($"Merged bytes: {oMergedBuffer.Length}")
|
||||||
|
|
||||||
|
' 8. Save and open
|
||||||
Dim desktopPath As String = Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
|
Dim desktopPath As String = Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
|
||||||
Dim oNewPath = Path.Combine(desktopPath, $"E{txtEnvelope.Text}_FullTest.burned.pdf")
|
Dim oMergedPath = Path.Combine(desktopPath, $"E{txtEnvelope.Text}_FullTest.merged.pdf")
|
||||||
File.WriteAllBytes(oNewPath, oNewBuffer)
|
File.WriteAllBytes(oMergedPath, oMergedBuffer)
|
||||||
log.AppendLine($"Output: {oNewPath}")
|
log.AppendLine($"Output: {oMergedPath}")
|
||||||
|
|
||||||
txtResult.Text = log.ToString()
|
txtResult.Text = log.ToString()
|
||||||
Process.Start(oNewPath)
|
Process.Start(oMergedPath)
|
||||||
|
|
||||||
Catch ex As Exception
|
Catch ex As Exception
|
||||||
txtResult.Text = $"ERROR: {ex.Message}{vbCrLf}{vbCrLf}{ex.ToString()}"
|
txtResult.Text = $"ERROR: {ex.Message}{vbCrLf}{vbCrLf}{ex.ToString()}"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
Imports EnvelopeGenerator.Domain.Constants
|
Imports EnvelopeGenerator.Domain.Constants
|
||||||
Imports EnvelopeGenerator.Domain.Entities
|
Imports EnvelopeGenerator.Domain.Entities
|
||||||
|
Imports EnvelopeGenerator.Domain.Interfaces
|
||||||
|
|
||||||
Public Class ReportItem
|
Public Class ReportItem
|
||||||
|
|
||||||
@@ -11,8 +12,15 @@ Public Class ReportItem
|
|||||||
Public Property ItemStatus As EnvelopeStatus
|
Public Property ItemStatus As EnvelopeStatus
|
||||||
Public ReadOnly Property ItemStatusTranslated As String
|
Public ReadOnly Property ItemStatusTranslated As String
|
||||||
Get
|
Get
|
||||||
Dim oStatus = ItemStatus.ToString()
|
Dim oStatusKey = ItemStatus.ToString()
|
||||||
Return My.Resources.Model.ResourceManager.GetString(oStatus)
|
|
||||||
|
If ItemStatus = EnvelopeStatus.DocumentSigned AndAlso
|
||||||
|
Envelope IsNot Nothing AndAlso
|
||||||
|
Envelope.IsReadAndConfirm() Then
|
||||||
|
oStatusKey = "DocumentSignedRaC"
|
||||||
|
End If
|
||||||
|
|
||||||
|
Return My.Resources.Model.ResourceManager.GetString(oStatusKey)
|
||||||
End Get
|
End Get
|
||||||
End Property
|
End Property
|
||||||
|
|
||||||
|
|||||||
@@ -168,6 +168,9 @@
|
|||||||
<data name="DocumentSigned" xml:space="preserve">
|
<data name="DocumentSigned" xml:space="preserve">
|
||||||
<value>Document signed</value>
|
<value>Document signed</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DocumentSignedRaC" xml:space="preserve">
|
||||||
|
<value>Read and confirmed</value>
|
||||||
|
</data>
|
||||||
<data name="Draft" xml:space="preserve">
|
<data name="Draft" xml:space="preserve">
|
||||||
<value>Draft</value>
|
<value>Draft</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -168,6 +168,9 @@
|
|||||||
<data name="DocumentSigned" xml:space="preserve">
|
<data name="DocumentSigned" xml:space="preserve">
|
||||||
<value>Dokument unterzeichnet</value>
|
<value>Dokument unterzeichnet</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DocumentSignedRaC" xml:space="preserve">
|
||||||
|
<value>Gelesen und bestätigt</value>
|
||||||
|
</data>
|
||||||
<data name="Draft" xml:space="preserve">
|
<data name="Draft" xml:space="preserve">
|
||||||
<value>Entwurf</value>
|
<value>Entwurf</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -7,9 +7,5 @@
|
|||||||
double X { get; set; }
|
double X { get; set; }
|
||||||
|
|
||||||
double Y { get; set; }
|
double Y { get; set; }
|
||||||
|
|
||||||
double Width { get; set; }
|
|
||||||
|
|
||||||
double Height { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -213,6 +213,15 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Gelesen und bestätigt.
|
||||||
|
/// </summary>
|
||||||
|
public static string DocumentSignedRaC {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("DocumentSignedRaC", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Entwurf.
|
/// Looks up a localized string similar to Entwurf.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -168,6 +168,9 @@
|
|||||||
<data name="DocumentSigned" xml:space="preserve">
|
<data name="DocumentSigned" xml:space="preserve">
|
||||||
<value>Dokument unterzeichnet</value>
|
<value>Dokument unterzeichnet</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DocumentSignedRaC" xml:space="preserve">
|
||||||
|
<value>Gelesen und bestätigt</value>
|
||||||
|
</data>
|
||||||
<data name="Draft" xml:space="preserve">
|
<data name="Draft" xml:space="preserve">
|
||||||
<value>Entwurf</value>
|
<value>Entwurf</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net462;net8.0-windows</TargetFrameworks>
|
<TargetFrameworks>net462;net7.0;net8.0;net9.0</TargetFrameworks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(TargetFramework)' == 'net462'">
|
<PropertyGroup Condition="'$(TargetFramework)' == 'net462'">
|
||||||
@@ -17,8 +17,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="GdPicture" Version="14.4.7" />
|
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" />
|
||||||
<PackageReference Include="GdPicture.runtimes.windows" Version="14.4.7" />
|
<PackageReference Include="itext" Version="8.0.5" />
|
||||||
|
<PackageReference Include="itext.bouncy-castle-adapter" Version="8.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
using EnvelopeGenerator.Domain.Interfaces;
|
using iText.Kernel.Pdf;
|
||||||
using GdPicture14;
|
using iText.Kernel.Pdf.Canvas;
|
||||||
|
using EnvelopeGenerator.Domain.Interfaces;
|
||||||
|
using iText.Kernel.Colors;
|
||||||
|
using DigitalData.Core.Abstractions.Interfaces;
|
||||||
|
|
||||||
#if NETFRAMEWORK
|
#if NETFRAMEWORK
|
||||||
using System;
|
using System;
|
||||||
@@ -12,26 +15,8 @@ namespace EnvelopeGenerator.PdfEditor
|
|||||||
{
|
{
|
||||||
public static class Pdf
|
public static class Pdf
|
||||||
{
|
{
|
||||||
private const string LicenseKey = "kG1Qf9PwmqgR8aDmIW2zI_ebj48RzqAJegRxcystEmkbTGQqfkNBdFOXIb6C_A00Ra8zZkrHdfjqzOPXK7kgkF2YDhvrqKfqh4WDug2vOt0qO31IommzkANSuLjZ4zmraoubyEVd25rE3veQ2h_j7tGIoH_LyIHmy24GaXsxdG0yCzIBMdiLbMMMDwcPY-809KeZ83Grv76OVhFvcbBWyYc251vou1N-kGg5_ZlHDgfWoY85gTLRxafjD3KS_i9ARW4BMiy36y8n7UP2jN8kGRnW_04ubpFtfjJqvtsrP_J9D0x7bqV8xtVtT5JI6dpKsVTiMgDCrIcoFSo5gCC1fw9oUopX4TDCkBQttO4-WHBlOeq9dG5Yb0otonVmJKaQA2tP6sMR-lZDs3ql_WI9t91yPWgpssrJUxSHDd27_LMTH_owJIqkF3NOJd9mYQuAv22oNKFYbH8e41pVKb8cT33Y9CgcQ_sy6YDA5PTuIRi67mjKge_nD9rd0IN213Ir9M_EFWqg9e4haWzIdHXQUo0md70kVhPX4UIH_BKJnxEEnFfoFRNMh77bB0N4jkcBEHPl-ghOERv8dOztf4vCnNpzzWvcLD2cqWIm6THy8XGGq9h4hp8aEreRleSMwv9QQAC7mjLwhQ1rBYkpUHlpTjhTLnMwHknl6HH0Z6zzmsgkRKVyfquv94Pd7QbQfZrRka0ss_48pf9p8hAywEn81Q==";
|
|
||||||
|
|
||||||
private static bool _initialized = false;
|
|
||||||
private static readonly object _initLock = new object();
|
|
||||||
|
|
||||||
internal static void EnsureInitialized()
|
|
||||||
{
|
|
||||||
if (_initialized) return;
|
|
||||||
lock (_initLock)
|
|
||||||
{
|
|
||||||
if (_initialized) return;
|
|
||||||
var licenseManager = new LicenseManager();
|
|
||||||
licenseManager.RegisterKEY(LicenseKey);
|
|
||||||
_initialized = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Pdf<MemoryStream, MemoryStream> FromMemory(byte[] documentBytes)
|
public static Pdf<MemoryStream, MemoryStream> FromMemory(byte[] documentBytes)
|
||||||
{
|
{
|
||||||
EnsureInitialized();
|
|
||||||
return new Pdf<MemoryStream, MemoryStream>(new MemoryStream(documentBytes), new MemoryStream());
|
return new Pdf<MemoryStream, MemoryStream>(new MemoryStream(documentBytes), new MemoryStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,7 +26,6 @@ namespace EnvelopeGenerator.PdfEditor
|
|||||||
#endif
|
#endif
|
||||||
outputStream = null)
|
outputStream = null)
|
||||||
{
|
{
|
||||||
EnsureInitialized();
|
|
||||||
return new Pdf<MemoryStream, MemoryStream>(stream, outputStream ?? new MemoryStream(), disposeInputStream: false, disposeOutputStream: outputStream is null);
|
return new Pdf<MemoryStream, MemoryStream>(stream, outputStream ?? new MemoryStream(), disposeInputStream: false, disposeOutputStream: outputStream is null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,114 +34,115 @@ namespace EnvelopeGenerator.PdfEditor
|
|||||||
where TInputStream : Stream
|
where TInputStream : Stream
|
||||||
where TOutputStream : Stream
|
where TOutputStream : Stream
|
||||||
{
|
{
|
||||||
|
private readonly PdfDocument _doc;
|
||||||
private readonly TInputStream _inputStream;
|
private readonly TInputStream _inputStream;
|
||||||
private readonly TOutputStream _outputStream;
|
private readonly TOutputStream _outputStream;
|
||||||
|
private readonly PdfReader _reader;
|
||||||
|
private readonly PdfWriter _writer;
|
||||||
|
|
||||||
private readonly bool _disposeInputStream;
|
private readonly bool _disposeInputStream;
|
||||||
private readonly bool _disposeOutputStream;
|
private readonly bool _disposeOutputStream;
|
||||||
|
|
||||||
// Holds the current PDF bytes; replaced after each Background() call
|
|
||||||
private byte[] _pdfBytes;
|
|
||||||
|
|
||||||
public Pdf(TInputStream inputStream, TOutputStream outputStream, bool disposeInputStream = true, bool disposeOutputStream = true)
|
public Pdf(TInputStream inputStream, TOutputStream outputStream, bool disposeInputStream = true, bool disposeOutputStream = true)
|
||||||
{
|
{
|
||||||
_inputStream = inputStream;
|
_inputStream = inputStream;
|
||||||
_outputStream = outputStream;
|
_outputStream = outputStream;
|
||||||
|
_reader = new PdfReader(inputStream);
|
||||||
|
_writer = new PdfWriter(outputStream);
|
||||||
|
_doc = new PdfDocument(_reader, _writer);
|
||||||
_disposeInputStream = disposeInputStream;
|
_disposeInputStream = disposeInputStream;
|
||||||
_disposeOutputStream = disposeOutputStream;
|
_disposeOutputStream = disposeOutputStream;
|
||||||
_pdfBytes = ReadAllBytes(inputStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] ReadAllBytes(Stream stream)
|
|
||||||
{
|
|
||||||
if (stream is MemoryStream ms)
|
|
||||||
return ms.ToArray();
|
|
||||||
|
|
||||||
using (var buf = new MemoryStream())
|
|
||||||
{
|
|
||||||
stream.CopyTo(buf);
|
|
||||||
return buf.ToArray();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Flushes the current PDF bytes to the output stream and returns it.
|
/// Gets the output stream containing the edited PDF document.
|
||||||
/// After calling this, the instance should not be modified further.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Accessing this property will close the underlying <see cref="PdfDocument"/> to ensure
|
||||||
|
/// all changes are flushed to the stream. After accessing this property, the PDF document
|
||||||
|
/// can no longer be modified using this <see cref="Pdf{TInputStream, TOutputStream}"/> instance.
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>
|
||||||
|
/// The <typeparamref name="TOutputStream"/> instance that contains the updated PDF bytes.
|
||||||
|
/// </returns>
|
||||||
public TOutputStream ExportStream()
|
public TOutputStream ExportStream()
|
||||||
{
|
{
|
||||||
_outputStream.SetLength(0);
|
_doc.Close();
|
||||||
_outputStream.Position = 0;
|
|
||||||
_outputStream.Write(_pdfBytes, 0, _pdfBytes.Length);
|
|
||||||
_outputStream.Position = 0;
|
|
||||||
return _outputStream;
|
return _outputStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
#region Edit methods
|
||||||
/// Draws the signature field background rectangles onto the PDF pages.
|
public Pdf<TInputStream, TOutputStream> Document(Action<PdfDocument> edit)
|
||||||
///
|
|
||||||
/// Coordinate system: GdPicture SetOrigin(PdfOriginTopLeft) + SetMeasurementUnit(PdfMeasurementUnitInch)
|
|
||||||
/// matches the Form app's GdPicture annotation coordinate system exactly.
|
|
||||||
/// DB values (X, Y in inches from top-left) are used directly — zero conversion.
|
|
||||||
/// </summary>
|
|
||||||
public Pdf<TInputStream, TOutputStream> Background<TSignature>(IEnumerable<TSignature> signatures, double widthInch = 1.9500000000000002, double heightInch = 2.52)
|
|
||||||
where TSignature : IDocReceiverElement
|
|
||||||
{
|
{
|
||||||
using (var gdpdf = new GdPicturePDF())
|
edit(_doc);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pdf<TInputStream, TOutputStream> Page(int pageIndex, Action<PdfPage> design)
|
||||||
|
=> Document(doc =>
|
||||||
{
|
{
|
||||||
GdPictureStatus loadStatus = gdpdf.LoadFromStream(new MemoryStream(_pdfBytes));
|
var page = doc.GetPage(pageIndex);
|
||||||
if (loadStatus != GdPictureStatus.OK)
|
design(doc.GetPage(pageIndex));
|
||||||
throw new InvalidOperationException("GdPicturePDF.LoadFromStream failed: " + loadStatus);
|
});
|
||||||
|
|
||||||
// Top-left origin + inch units = same coordinate system as GdPicture Form app annotations.
|
public Pdf<TInputStream, TOutputStream> Page(Action<PdfPage> design)
|
||||||
// DB X and Y (inches from top-left) can be used as-is, no conversion needed.
|
{
|
||||||
gdpdf.SetOrigin(PdfOrigin.PdfOriginTopLeft);
|
for (int i = 1; i <= _doc.GetNumberOfPages(); i++)
|
||||||
gdpdf.SetMeasurementUnit(PdfMeasurementUnit.PdfMeasurementUnitInch);
|
Page(i, design);
|
||||||
|
|
||||||
// Bottom accent line height: 2.5 PDF points converted to inches (1 inch = 72 pt)
|
|
||||||
const float bottomLineHeightInch = 2.5f / 72.0f;
|
|
||||||
|
|
||||||
foreach (var sig in signatures)
|
|
||||||
{
|
|
||||||
gdpdf.SelectPage(sig.Page);
|
|
||||||
|
|
||||||
// sig.Width/Height come from DB (the actual stored dimensions).
|
|
||||||
// Fall back to the method parameters only if the element has no stored size.
|
|
||||||
float w = (float)widthInch; // sig.Width > 0 ? (float)sig.Width : (float)widthInch;
|
|
||||||
float h = (float)heightInch; // sig.Height > 0 ? (float)sig.Height : (float)heightInch;
|
|
||||||
|
|
||||||
// annotation.Left/Top store the CENTER of the bounding box (GdPicture spec).
|
|
||||||
// DrawRectangle expects the TOP-LEFT corner → subtract half dimensions.
|
|
||||||
float x = (float)sig.X;
|
|
||||||
float y = (float)sig.Y;
|
|
||||||
|
|
||||||
// Background fill — DeviceRgb(222, 220, 215)
|
|
||||||
// DrawRectangle(Left, Top, Width, Height, Fill, Stroke)
|
|
||||||
gdpdf.SetFillColor(222, 220, 215);
|
|
||||||
gdpdf.DrawRectangle(x, y, w, h, true, false);
|
|
||||||
|
|
||||||
// Bottom accent line — DeviceRgb(204, 202, 198)
|
|
||||||
gdpdf.SetFillColor(204, 202, 198);
|
|
||||||
gdpdf.DrawRectangle(x, y + h - bottomLineHeightInch, w, bottomLineHeightInch, true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
var outStream = new MemoryStream();
|
|
||||||
GdPictureStatus saveStatus = gdpdf.SaveToStream(outStream);
|
|
||||||
gdpdf.CloseDocument();
|
|
||||||
|
|
||||||
if (saveStatus != GdPictureStatus.OK)
|
|
||||||
throw new InvalidOperationException("GdPicturePDF.SaveToStream failed: " + saveStatus);
|
|
||||||
|
|
||||||
_pdfBytes = outStream.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Dispose
|
public Pdf<TInputStream, TOutputStream> Design(int pageIndex, Action<PdfCanvas> design)
|
||||||
|
=> Document(doc =>
|
||||||
|
{
|
||||||
|
var page = doc.GetPage(pageIndex);
|
||||||
|
var canvas = new PdfCanvas(page);
|
||||||
|
design(canvas);
|
||||||
|
});
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public Pdf<TInputStream, TOutputStream> Background<TSignature>(IEnumerable<TSignature> signatures, double widthPx = 1.9500000000000002, double heightPx = 2.52)
|
||||||
|
where TSignature : IDocReceiverElement
|
||||||
|
{
|
||||||
|
foreach (var signature in signatures)
|
||||||
|
Page(signature.Page, page =>
|
||||||
|
{
|
||||||
|
var canvas = new PdfCanvas(page);
|
||||||
|
canvas.SaveState();
|
||||||
|
canvas.ConcatMatrix(1, 0, 0, -1, 0, page.GetPageSize().GetHeight());
|
||||||
|
|
||||||
|
double inchFactor = 72;
|
||||||
|
double magin = .2;
|
||||||
|
double x = (signature.X - .7 - magin) * inchFactor;
|
||||||
|
double y = (signature.Y - .5 - magin) * inchFactor;
|
||||||
|
double width = widthPx * inchFactor;
|
||||||
|
double height = heightPx * inchFactor;
|
||||||
|
|
||||||
|
double bottomLineLength = 2.5;
|
||||||
|
|
||||||
|
// draw background
|
||||||
|
canvas.SetFillColor(new DeviceRgb(222, 220, 215))
|
||||||
|
.Rectangle(x, y, width, height)
|
||||||
|
.Fill();
|
||||||
|
|
||||||
|
// draw bottom line
|
||||||
|
canvas.SetFillColor(new DeviceRgb(204, 202, 198))
|
||||||
|
.Rectangle(x, y + height - bottomLineLength, width, bottomLineLength)
|
||||||
|
.Fill();
|
||||||
|
|
||||||
|
canvas.RestoreState();
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Finalizer
|
||||||
private bool _disposed = false;
|
private bool _disposed = false;
|
||||||
|
|
||||||
~Pdf()
|
~Pdf()
|
||||||
{
|
{
|
||||||
|
// If Dispose is not called, clean up unmanaged resources
|
||||||
Dispose(false);
|
Dispose(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,12 +154,23 @@ namespace EnvelopeGenerator.PdfEditor
|
|||||||
|
|
||||||
private void Dispose(bool disposing)
|
private void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (_disposed) return;
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
|
// Managed resources
|
||||||
|
_doc?.Close();
|
||||||
if (_disposeInputStream) _inputStream?.Dispose();
|
if (_disposeInputStream) _inputStream?.Dispose();
|
||||||
if (_disposeOutputStream) _outputStream?.Dispose();
|
if(_disposeOutputStream) _outputStream?.Dispose();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// When called by the finalizer, clean up only unmanaged resources
|
||||||
|
// Unmanaged resources such as PdfDocument, PdfReader, and PdfWriter are already IDisposable; we close them here
|
||||||
|
try { _doc?.Close(); } catch { }
|
||||||
|
try { if(_disposeInputStream) _inputStream?.Dispose(); } catch { }
|
||||||
|
try { if (_disposeOutputStream) _outputStream?.Dispose(); } catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
@@ -182,14 +178,16 @@ namespace EnvelopeGenerator.PdfEditor
|
|||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
if (_disposed) return;
|
if (_disposed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_doc?.Close();
|
||||||
if (_disposeInputStream)
|
if (_disposeInputStream)
|
||||||
{
|
{
|
||||||
if (_inputStream is IAsyncDisposable asyncInput)
|
if (_inputStream is IAsyncDisposable asyncInput)
|
||||||
await asyncInput.DisposeAsync();
|
await asyncInput.DisposeAsync();
|
||||||
else
|
else
|
||||||
_inputStream?.Dispose();
|
_inputStream.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_disposeOutputStream)
|
if (_disposeOutputStream)
|
||||||
@@ -205,4 +203,4 @@ namespace EnvelopeGenerator.PdfEditor
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<PackageId>EnvelopeGenerator.Web</PackageId>
|
<PackageId>EnvelopeGenerator.Web</PackageId>
|
||||||
@@ -2169,7 +2169,6 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" />
|
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" />
|
||||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.11" />
|
|
||||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.82.1" />
|
<PackageReference Include="Microsoft.Identity.Client" Version="4.82.1" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ EndProject
|
|||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{134D4164-B291-4E19-99B9-E4FA3AFAB62C}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{134D4164-B291-4E19-99B9-E4FA3AFAB62C}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
COPILOT_CONTEXT.md = COPILOT_CONTEXT.md
|
COPILOT_CONTEXT.md = COPILOT_CONTEXT.md
|
||||||
|
fix-report-label-read-and-confirmed.md = fix-report-label-read-and-confirmed.md
|
||||||
FORM_APPLICATION_CONTEXT.md = FORM_APPLICATION_CONTEXT.md
|
FORM_APPLICATION_CONTEXT.md = FORM_APPLICATION_CONTEXT.md
|
||||||
RECEIVER_PDF_VIEWER_CONTEXT.md = RECEIVER_PDF_VIEWER_CONTEXT.md
|
RECEIVER_PDF_VIEWER_CONTEXT.md = RECEIVER_PDF_VIEWER_CONTEXT.md
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
|
|||||||
135
fix-report-label-read-and-confirmed.md
Normal file
135
fix-report-label-read-and-confirmed.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# Fix: Report Label "Read and confirmed" for Read and Sign Envelopes
|
||||||
|
|
||||||
|
## Status
|
||||||
|
✅ **COMPLETED**
|
||||||
|
|
||||||
|
## Git Branch
|
||||||
|
```
|
||||||
|
fix/report-label-read-and-confirmed
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
In the **Signature Certificate report** (`Signierungszertifikat`), the history event
|
||||||
|
list always showed **"Document signed"** for status `DocumentSigned` (code `2005`) <20>
|
||||||
|
even when the envelope type was **"Read and Sign"** (`EnvelopeTypeId = 2`).
|
||||||
|
|
||||||
|
For "Read and Sign" envelopes the correct label must be **"Read and confirmed"**
|
||||||
|
(DE: **"Gelesen und best<73>tigt"**).
|
||||||
|
|
||||||
|
### Screenshot reference
|
||||||
|
The report shows a table with columns *Ereignis | Benutzer | Zeitstempel*.
|
||||||
|
The row with status 2005 displayed "Document signed" where "Read and confirmed"
|
||||||
|
was expected.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
`ReportItem.ItemStatusTranslated` in
|
||||||
|
`EnvelopeGenerator.CommonServices\Jobs\FinalizeDocument\ReportItem.vb`
|
||||||
|
resolved the display label by calling:
|
||||||
|
|
||||||
|
```vb
|
||||||
|
My.Resources.Model.ResourceManager.GetString(ItemStatus.ToString())
|
||||||
|
```
|
||||||
|
|
||||||
|
There was **no branching logic** to handle the Read and Sign envelope type.
|
||||||
|
The same `DocumentSigned` key was always used regardless of envelope type.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
### Convention followed
|
||||||
|
The project already uses a `RaC` (**R**ead **a**nd **C**onfirm) suffix for
|
||||||
|
Read-and-Sign label variants across all resource files:
|
||||||
|
|
||||||
|
| Existing RaC keys |
|
||||||
|
|---|
|
||||||
|
| `DocumentRejectedRaC` |
|
||||||
|
| `EnvelopeReportCreatedRaC` |
|
||||||
|
| `MessageConfirmationSentRaC` |
|
||||||
|
|
||||||
|
### New resource key added: `DocumentSignedRaC`
|
||||||
|
|
||||||
|
| Resource file | Value |
|
||||||
|
|---|---|
|
||||||
|
| `Model.resx` (DE, default) | `Gelesen und best<73>tigt` |
|
||||||
|
| `Model.en.resx` (EN) | `Read and confirmed` |
|
||||||
|
|
||||||
|
### Logic change in `ReportItem.vb`
|
||||||
|
|
||||||
|
```vb
|
||||||
|
Public ReadOnly Property ItemStatusTranslated As String
|
||||||
|
Get
|
||||||
|
Dim oStatusKey = ItemStatus.ToString()
|
||||||
|
|
||||||
|
If ItemStatus = EnvelopeStatus.DocumentSigned AndAlso
|
||||||
|
Envelope IsNot Nothing AndAlso
|
||||||
|
Envelope.IsReadAndConfirm() Then
|
||||||
|
oStatusKey = "DocumentSignedRaC"
|
||||||
|
End If
|
||||||
|
|
||||||
|
Return My.Resources.Model.ResourceManager.GetString(oStatusKey)
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
```
|
||||||
|
|
||||||
|
`Envelope.IsReadAndConfirm()` is defined in
|
||||||
|
`EnvelopeGenerator.Domain\Interfaces\IEnvelope.cs` and returns `true`
|
||||||
|
when `EnvelopeTypeId == 2`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Changed
|
||||||
|
|
||||||
|
| # | File | Change | Status |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | `EnvelopeGenerator.CommonServices\Jobs\FinalizeDocument\ReportItem.vb` | Added `RaC` branching logic to `ItemStatusTranslated` | ? Done |
|
||||||
|
| 2 | `EnvelopeGenerator.Domain\Resources\Model.resx` | Added `DocumentSignedRaC` = `Gelesen und bestätigt` | ✅ Done |
|
||||||
|
| 3 | `EnvelopeGenerator.CommonServices\Strings\Model.resx` | Added `DocumentSignedRaC` = `Gelesen und bestätigt` | ✅ Done |
|
||||||
|
| 4 | `EnvelopeGenerator.CommonServices\Strings\Model.en.resx` | Added `DocumentSignedRaC` = `Read and confirmed` | ✅ Done |
|
||||||
|
| 5 | `EnvelopeGenerator.Domain\Resources\Model.Designer.cs` | Added `DocumentSignedRaC` strongly-typed property | ✅ Done |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How to resume (for next agent)
|
||||||
|
|
||||||
|
All pending items are **resource file additions only**.
|
||||||
|
Each file needs one new `<data>` block inserted **directly after** the existing
|
||||||
|
`DocumentSigned` entry:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<data name="DocumentSignedRaC" xml:space="preserve">
|
||||||
|
<value>Gelesen und best<73>tigt</value> <!-- DE files -->
|
||||||
|
<!-- OR -->
|
||||||
|
<value>Read and confirmed</value> <!-- EN file -->
|
||||||
|
</data>
|
||||||
|
```
|
||||||
|
|
||||||
|
`Model.Designer.cs` also needs a new strongly-typed property added after
|
||||||
|
`DocumentSigned`:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Gelesen und best<73>tigt.
|
||||||
|
/// </summary>
|
||||||
|
public static string DocumentSignedRaC {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("DocumentSignedRaC", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
After changes, generate a Signature Certificate report for a **Read and Sign**
|
||||||
|
envelope and verify:
|
||||||
|
- History row with status 2005 shows **"Read and confirmed"** (EN)
|
||||||
|
- History row with status 2005 shows **"Gelesen und best<73>tigt"** (DE)
|
||||||
|
- Regular (non-RaC) envelopes still show **"Document signed"** / **"Dokument unterzeichnet"**
|
||||||
Reference in New Issue
Block a user