Compare commits

..

82 Commits

Author SHA1 Message Date
36b03da084 fix(DependencyInjection): change AnnotationManager registration from Scoped to Transient 2025-11-07 15:21:36 +01:00
defa53fa26 refactor(BurnPdfCommand): rename SourceBuffer to Document in BurnPdfCommand and handler
- Renamed parameter `SourceBuffer` to `Document` in BurnPdfCommand record
- Updated all usages of `request.SourceBuffer` to `request.Document`
- Reorganized method order for better readability (Handle moved up)
- No functional or behavioral changes introduced
2025-11-07 14:56:34 +01:00
6134b58a4c refactor(BurnPdfCommand): extract GdPicture and math helpers into extension classes and simplify BurnPdfCommandHandler
- Refactored BurnPdfCommandHandler to use new extension methods for cleaner annotation handling.
- Introduced ITextStyle interface to generalize font styling for text annotations.
- Updated PDFBurnerParams to implement ITextStyle for consistent font settings reuse.
- Added MathematExtensions for coordinate and unit conversion (ToInches, ToPointF).
- Added GdPictureExtensions to encapsulate annotation-related logic (form fields, images, ink).
- Improved readability and maintainability by removing redundant helper methods.
2025-11-07 14:46:55 +01:00
5299016b43 move PSDPFKitModels namespace to Dto and rename as PSPDFKitInstant 2025-11-07 13:51:53 +01:00
55c20e83d8 refactor: convert BurnPdfCommand to record and update handler to return byte[]
- Changed `BurnPdfCommand` from empty class to a `record` with properties: `SourceBuffer`, `InstantJSONList`, `EnvelopeId`.
- Updated `BurnPdfCommandHandler` to implement `IRequestHandler<BurnPdfCommand, byte[]>`.
- Modified `Handle` method to be async and use the `request` data to return PDF bytes.
- Removed obsolete `BurnPdfCommand` class and updated related logic accordingly.
2025-11-07 13:44:53 +01:00
f1a140faa7 feat(BurnPdfCommandHandler): implement AddInkAnnotation for Annotation parameter
- Added implementation for AddInkAnnotation(Annotation) to handle ink annotations from Annotation objects.
- Retained existing AddInkAnnotation(int, string) method for backward compatibility.
- Refactored PDF burning logic to support both Annotation-based and JSON-based annotations.
2025-11-07 13:24:26 +01:00
b20d25e5b9 feat(BurnPdfCommandHandler): implement AddInkAnnotation for freehand annotations
- Added implementation for AddInkAnnotation(int page, string value) to render ink annotations from JSON.
- Deserialize ink lines and stroke color, convert points to PDF coordinates, and add as freehand annotations.
- Retained placeholder for AddInkAnnotation(Annotation pAnnotation) for future use.
2025-11-07 13:21:33 +01:00
e21eb2c0d6 refactor(BurnPdfCommandHandler): rename AddInstantJSONAnnotationToPdf to AddInstantJsonAnnotationToPdf for consistent casing 2025-11-07 13:16:14 +01:00
d237b4ab95 feat(BurnPdfCommand): implement AddImageAnnotation with attachment handling
- Added logic to AddImageAnnotation to handle image attachments from a dictionary.
- Removed NotImplementedException placeholder for AddImageAnnotation with attachments.
- Refactored code to convert annotation bounds from pixels to inches before adding images.
2025-11-07 13:14:02 +01:00
dff99b163d refactor: simplify AddFormFieldValue method and remove redundant parameters 2025-11-07 13:12:01 +01:00
70fbf31b14 feat(BurnPdfCommandHandler): implement AddImageAnnotation for base64 images
- Added implementation for AddImageAnnotation(double x, double y, double width, double height, int page, string base64)
- Allows embedding images directly from base64 into PDF pages
- Existing annotation burning logic remains unchanged
2025-11-07 13:09:54 +01:00
339e0e81cd feat(PDFBurnerParams): add DefaultIndexOfAnnot and GetAnnotationIndex
- add `DefaultIndexOfAnnot` property to provide a fallback index for annotations
 - add `GetAnnotationIndex(string name)` method to safely retrieve an annotation index, returning default if not found
2025-11-07 13:03:50 +01:00
a386ad72bb refactor(BurnPdfCommandHandler): use IOptions<PDFBurnerParams> and improve form field handling
- Updated BurnPdfCommandHandler constructor to accept IOptions<PDFBurnerParams> instead of raw PDFBurnerParams.
- Updated AddFormFieldValue to calculate coordinates based on PDFBurnerParams offsets and margins.
- Added helper methods for converting pixels to inches (ToInches, ToPointF).
- Removed unused NotImplemented methods in AddFormFieldValue overload.
- Improved code readability and maintainability for annotation burning logic.
2025-11-07 12:59:13 +01:00
02c5f286ec feat(pdf): implement AddFormFieldValue for PDF annotations
- Added implementation for AddFormFieldValue method to properly add text annotations to PDF pages using AnnotationManager.
- Preserves font settings from PDFBurnerParams.
- Other annotation methods remain unimplemented.
2025-11-07 12:44:05 +01:00
5fa358ca79 refactor(BurnPdfCommandHandler): deserialize and process instant JSON annotations
- Added Newtonsoft.Json to parse instant JSON into `InstantData`.
- Replaced `AddInstantJSONAnnotationToPDF` logic with `AddInstantJsonAnnotationToPdf`.
- Process annotations by type (Image, Ink, Widget) and apply attachments/form fields.
- Introduced checks for ignored form field values using `_pdfBurnerParams.IgnoredLabels`.
- Added logging for annotation IDs when processing instant JSON.
2025-11-07 12:36:19 +01:00
7233d2ce98 add InstantData 2025-11-07 11:37:28 +01:00
5784cc7a97 refactor(Application.Pdf.PSPDFKit): rename as Application.Pdf.PSPDFKitModels 2025-11-07 11:28:55 +01:00
f31f680f91 add PSPDFKit.Annotation 2025-11-07 11:27:07 +01:00
e7c2d46ef0 add PSPDFKit.Ink 2025-11-07 11:23:04 +01:00
85d70c1db4 feat(PDFBurnerParams): add IndexOfAnnot-dictionary 2025-11-07 11:16:22 +01:00
fb340fb08a create PSPDFKit.Lines 2025-11-07 10:59:13 +01:00
7d3959ae51 add PSPDFKit.Attachment 2025-11-07 10:56:53 +01:00
9d4890b10d add PSPDFKit.FormFieldValue 2025-11-07 10:52:15 +01:00
b64f4d71f5 feat(BurnPdfCommandHandler): add logging and implement BurnInstantJSONAnnotsToPDF
- Added ILogger<BurnPdfCommandHandler> to log warnings during annotation processing
- Implemented BurnInstantJSONAnnotsToPDF method to handle instant JSON annotations
- Added AddInstantJSONAnnotationToPDF stub for future implementation
- Updated PDF burn flow to include error handling and logging
2025-11-07 10:40:44 +01:00
b7d146ddb5 refactor: standardize annotation types and clean up PDF burn handler
- Introduced `AnnotationType.PSPDFKit` constants for consistent annotation type references.
- Updated `BurnPdfCommandHandler` to use new PSPDFKit annotation types.
- Refactored method signatures and local variables for clarity (e.g., using 'var', null-coalescing for annotation properties).
- Streamlined `using` statements and memory management in PDF burning process.
- Added placeholder methods for form fields, image, and ink annotations to centralize future implementations.
2025-11-07 10:11:48 +01:00
6a5c9a3489 fix(BurnPdfCommand): improve JSON serialization and null safety in Signature entity and BurnPdfCommandHandler
- Added System.Text.Json.Serialization namespace and [JsonIgnore] attributes to Top and Left properties in Signature entity for .NET builds
- Applied null-forgiving operator (!) and null-coalescing defaults for frame.X and frame.Y in BurnPdfCommandHandler
- Improved runtime safety and prevented unintended JSON serialization of computed properties
2025-11-07 09:51:02 +01:00
Developer 02
15f6ee7be0 add ExportDocumentException 2025-11-06 21:53:36 +01:00
Developer 02
1051c5356f add CreateReportException 2025-11-06 21:52:35 +01:00
Developer 02
7b13350fbf add BurnAnnotationException 2025-11-06 21:51:20 +01:00
Developer 02
2bada327d8 add MergeDocumentException 2025-11-06 21:50:17 +01:00
Developer 02
a47729ebca feat(pdf): implement annotation burning logic in BurnPdfCommandHandler
- Added full implementation of BurnElementAnnotsToPDF method
- Integrated PdfEditor for background rendering
- Added annotation handling for form fields, images, and ink types
- Included error handling for PDF loading and saving
- Removed unused DigitalData.Core.Abstractions namespace
2025-11-06 21:49:24 +01:00
Developer 02
fcfed963b7 feat(pdf): add annotation burning logic and repository dependency to BurnPdfCommandHandler
- Inject IRepository<Signature> for accessing signature and annotation data
- Implement BurnAnnotsToPDF method to process annotations from DB or JSON
- Add BurnElementAnnotsToPDF and BurnInstantJSONAnnotsToPDF stubs
- Integrate EF Core Include for loading related entities
2025-11-06 21:30:51 +01:00
Developer 02
b2ace61cd4 refactor(BurnPdfCommandHandler): inject AnnotationManager 2025-11-06 21:20:57 +01:00
Developer 02
77cdc83a4e add AnnotationManager as scoped 2025-11-06 21:19:05 +01:00
Developer 02
a611df4914 inject LicenseManager 2025-11-06 21:09:46 +01:00
Developer 02
1341f69ab1 move gdpucture parmas configuration to application layer 2025-11-06 20:57:10 +01:00
Developer 02
0480513288 add gdpicture dependencies 2025-11-06 20:10:41 +01:00
Developer 02
9dbd1b16b5 inject pdf burner params 2025-11-06 19:57:56 +01:00
Developer 02
c649c93921 move pdf burner to application layer 2025-11-06 19:51:15 +01:00
Developer 02
1843119b1b add PDFBurnerParams 2025-11-06 19:39:43 +01:00
Developer 02
bc4371cb99 feat(PDFBurner): init for only .net 2025-11-06 19:34:39 +01:00
Developer 02
05130d6163 feat(FinishEnvelopeJob): enhance FinishEnvelopeJob logging for finalized envelopes
- Removed redundant initial info log.
- Added detailed logging after job execution:
  - Logs total finalized envelope count.
  - Logs UUIDs of finalized envelopes when available.
  - Logs success message when no envelopes were finalized.
2025-11-06 09:59:16 +01:00
567b9c9565 refactor(Envelope): remove unnecessary [NotMapped] attribute from DocResult property 2025-11-05 16:49:39 +01:00
b798181f91 feat(notification): implement handler to remove document result on signature removal 2025-11-05 15:26:48 +01:00
7e8fc25ec9 refactor: update core inf and abst.app 2025-11-05 14:31:24 +01:00
db76162697 Refactor: Convert envelope query filters to LINQ query syntax
- Replaced multiple sequential 'Where' calls with a single LINQ query expression
- Preserved all status filters (Include, Ignore, Min, Max)
- Preserved DocResult filtering logic
- Improves readability while keeping behavior identical
2025-11-05 13:39:10 +01:00
2b4573ea73 feat(ReadEnvelopeQuery): implement ReadEnvelopeQueryHandler with filtering support
- Added `ReadEnvelopeQueryHandler` to handle `ReadEnvelopeQuery`.
- Supports filtering envelopes by:
  - Included and ignored `EnvelopeStatus` values.
  - Minimum and maximum status.
  - Presence or absence of `DocResult`.
- Uses repository query extension methods and AutoMapper to map to `EnvelopeDto`.
2025-11-05 13:32:30 +01:00
4a043ed247 feat(FinishEnvelopeJob): update Execute to add cancelation token 2025-11-05 12:58:35 +01:00
a62a035ec6 feat(FinishEnvelopeJob): enhance FinishEnvelopeJob to fetch signed envelopes
- Added MediatR dependency to query envelopes
- Injected GdPictureOptions via IOptions
- Updated Execute method to fetch envelopes with status 'EnvelopeCompletelySigned'
- Preserved logging with job details
- Prepared loop for further processing of envelopes
2025-11-05 12:55:49 +01:00
1713a65014 refactor(Program): simplify Quartz job registration and add EnvelopeTaskApiJob
- Replaced manual FinishEnvelopeJob Quartz setup with ScheduleJobDefault extension
- Added scheduling for EnvelopeTaskApiJob
- Updated using directives to include EnvelopeGenerator.Finalizer namespace
- Improved maintainability by removing redundant Quartz configuration logic
2025-11-05 11:24:35 +01:00
695d7c83e0 feat(Extensions): add Quartz job scheduling extension methods
- Introduced ScheduleJobDefault<TJob> extension for IServiceCollectionQuartzConfigurator
- Allows scheduling jobs using either a cron expression or configuration-based cron settings
- Includes validation for missing or invalid cron expressions
2025-11-05 10:43:25 +01:00
428f71863d create EnvelopeTaskApi 2025-11-05 09:41:02 +01:00
cce2f8f90e refactor(Worker): rename as FinishEnvelopeJob 2025-11-05 09:34:56 +01:00
Developer 02
86c9fdfcd7 refactor: inject IScheduler via DI instead of using StdSchedulerFactory directly 2025-11-04 17:34:36 +01:00
Developer 02
89ec887510 feat(quartz): integrate Quartzmon dashboard and CommandDotNet
- Added `Quartzmon` package and configured its middleware for job monitoring.
- Integrated `CommandDotNet.Execution` for command-line execution support.
- Updated using directives and service registrations accordingly.
- Preserved existing Serilog logging, DB context, and EnvelopeGenerator setup.
2025-11-04 17:29:07 +01:00
Developer 02
7d5b988842 fix(middleware): add UseRouting before UseAuthorization
Added `app.UseRouting()` in the middleware pipeline to ensure proper endpoint routing before authorization and controller mapping.
2025-11-04 17:18:29 +01:00
Developer 02
3c456562cc refactor: replace QuartzHostedService with QuartzServer and remove unnecessary using
- Replaced `AddQuartzHostedService` with `AddQuartzServer` for better Quartz integration.
- Removed `Microsoft.Extensions.Options` using as it was unused.
- Updated Quartz job naming to remove GUID and simplify identity.
- Minor code cleanup in using statements and regions.
2025-11-04 17:06:00 +01:00
Developer 02
4d6b01030c refactor(startup): migrate from generic Host to WebApplication and integrate Web API support
- Replaced Host.CreateApplicationBuilder with WebApplication.CreateBuilder
- Added Web API service registrations (Controllers, Swagger)
- Organized startup into clear regions: Logging, Configuration, Worker, Services, Middleware
- Introduced Swagger and HTTPS middleware for API
- Improved structure and readability of Program.cs
2025-11-04 16:12:21 +01:00
Developer 02
75e7e9925b feat(Program): make Quartz cron schedule configurable via appsettings
- Replaced hardcoded cron expression with configuration-based `Worker:CronExpression`.
- Throws descriptive exception if cron expression is missing.
- Keeps previous worker and DB context setup unchanged.
2025-11-04 15:37:20 +01:00
Developer 02
0a175b9e9d refactor: remove unnecessary while loop in Worker.Execute 2025-11-04 15:18:38 +01:00
Developer 02
f611e74de1 refactor(config): allow GdPicture license key from configuration
- Updated GdPictureOptions setup to read license key from `GdPictureLicenseKey` config value.
- Falls back to reading from third-party module if config key is not set.
2025-11-04 14:56:44 +01:00
Developer 02
08ca116628 refactor(worker): replace BackgroundService with Quartz IJob for scheduled execution
- Removed inheritance from BackgroundService
- Implemented Quartz IJob interface for better scheduling control
- Replaced ExecuteAsync with Execute(IJobExecutionContext)
- Updated cancellation handling to use context.CancellationToken
2025-11-04 14:49:56 +01:00
Developer 02
4997f7d75c feat: add in-memory database support via appsettings UseInMemoryDb flag
- Introduced conditional EF Core configuration to support InMemoryDatabase for testing or lightweight runs.
- Added `UseInMemoryDb` config flag read from appsettings.
- Retained SQL Server as the default when the flag is false.
- Added missing Quartz namespace import.
2025-11-04 13:44:39 +01:00
b5cd42b6fa add WorkerOptions.
- bind IntervalInMin with worker task delay
2025-11-03 16:38:41 +01:00
187f4a42fc feat(GdPictureOptions): Created to configure parameters related to GdPicture.
- Configure the GdPicture license key via a third-party module entity.
2025-11-03 15:36:11 +01:00
23d4b2f31e chore: move finalizer to presentation layer 2025-11-03 14:51:13 +01:00
8e71e5b4bb feat(third-party-modules): add query and handler for reading third-party modules with filtering by name and active status 2025-11-03 14:49:54 +01:00
b693615561 feat(ThirdPartyModuleDto): create DTO of ThirdPartyModule with mapping profile 2025-11-03 14:30:45 +01:00
36dc9266bc Add ThirdPartyModule entity for 3rd party module tracking
- Created ThirdPartyModule class in EnvelopeGenerator.Domain.Entities
- Mapped to TBDD_3RD_PARTY_MODULES table with appropriate columns
- Added properties: Id, Active, Name, Description, License, Version, AddedWho, AddedWhen, ChangedWho, ChangedWhen
- Configured data annotations for primary key, required fields, string lengths, and nullable support
- Conditional nullable support based on compilation symbols
2025-11-03 13:05:24 +01:00
9aabe270b4 add appsettings for PDF burner 2025-11-03 11:56:10 +01:00
b303b7be06 chore(Finalizer.Program): enhance configuration and environment-specific JSON loading
- Changed Serilog configuration file from `appsettings.json` to `appsettings.Logging.json`.
- Added logging for application startup.
- Dynamically load environment-specific appsettings JSON files, excluding `Development` and `migration` files.
2025-11-03 11:41:55 +01:00
02937360ea chore(Finalizer.appsettings): update Serilog configuration to increase log verbosity and retention
- Changed default minimum log level from Information to Verbose.
- Updated console sink to use Verbose level.
- Renamed and consolidated log files for Verbose, Debug, Info, Warning, Error, and Fatal levels.
- Increased retained file count from 7 to 30 for all log levels.
2025-11-03 11:26:14 +01:00
c2735b92e0 feat(logging): enhance Serilog configuration with separate level-based log files 2025-11-03 10:48:35 +01:00
568f43186c refactor(Finalizer.Program): simplify EnvelopeGenerator service registration using AddEnvelopeGenerator extension
- Replaced manual service setup with AddEnvelopeGenerator fluent configuration
- Added EnvelopeGenerator.DependencyInjection namespace
- Integrated distributed SQL Server cache for EG services
- Improved DI structure and reduced obsolete warnings
2025-11-03 10:24:09 +01:00
3bc5439b5a feat(dependency-injection): add service configuration validation and simplify setup
- Removed unnecessary IConfiguration and SqlServerCacheOptions parameters from AddEnvelopeGenerator
- Added EnsureAllServicesConfigured() to validate that all required service methods are invoked
- Introduced _addingStatus dictionary to track configuration status
- Renamed internal queue field to _serviceRegs for consistency
- Added AddServices() method to explicitly register EnvelopeGenerator services
- Improved AddLocalization() to support optional custom localization options
2025-11-03 09:30:43 +01:00
22b494a262 refactor(di): unify Application and Infrastructure DI registrations under a central method
- Added central AddEnvelopeGenerator extension to aggregate existing DI setups
- Introduced EGConfiguration for modular service registration
- Standardized configuration pattern for Application and Infrastructure layers
- Simplified distributed cache and localization registration
2025-11-03 08:44:22 +01:00
209785dda5 refactor(DependencyInjection): created to handle DependencyInjection 2025-10-31 11:31:39 +01:00
44ea893f05 move memory-cache injection to Application-layer 2025-10-31 10:58:22 +01:00
b4aa7984aa chore(Application): Update your packages with vulnerabilities
- Added `SixLabors.ImageSharp` (v3.1.12) to main package references.
- Added `System.Formats.Asn1` for all target frameworks:
  - net7.0 → v8.0.2
  - net8.0, net9.0 → v9.0.10
2025-10-31 10:34:25 +01:00
7c5a505ad1 add serilog 2025-10-31 10:23:11 +01:00
bd6d57e1e8 feat: integrate EnvelopeGenerator infrastructure, DB context, and services
- Added references to EnvelopeGenerator.Application, Infrastructure, and EF Core.
- Configured DB context with SQL Server connection string from configuration.
- Registered EnvelopeGenerator services and infrastructure with dependency injection.
- Preserved warnings suppression for obsolete members.
- Structured DI registration under a dedicated region for clarity.
2025-10-30 17:05:45 +01:00
8403ce2c6a init EnvelopeGenerator.Finalizer 2025-10-30 16:31:22 +01:00
73 changed files with 2032 additions and 409 deletions

View File

@@ -0,0 +1,12 @@
namespace EnvelopeGenerator.Application.Common.Configurations;
/// <summary>
///
/// </summary>
public class GdPictureParams
{
/// <summary>
///
/// </summary>
public string License { get; set; } = null!;
}

View File

@@ -0,0 +1,73 @@
using EnvelopeGenerator.Application.Common.Interfaces.Model;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
namespace EnvelopeGenerator.Application.Common.Configurations;
/// <summary>
///
/// </summary>
public class PDFBurnerParams : ITextStyle
{
/// <summary>
///
/// </summary>
public IEnumerable<string> IgnoredLabels { get; set; } = new List<string>
{
"Date", "Datum", "ZIP", "PLZ", "Place", "Ort", "Position", "Stellung"
};
/// <summary>
///
/// </summary>
public double TopMargin { get; set; } = 0.1;
/// <summary>
///
/// </summary>
public double YOffset { get; set; } = -0.3;
/// <summary>
///
/// </summary>
public string FontName { get; set; } = "Arial";
/// <summary>
///
/// </summary>
public int FontSize { get; set; } = 8;
/// <summary>
///
/// </summary>
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
public FontStyle FontStyle { get; set; } = FontStyle.Italic;
/// <summary>
///
/// </summary>
public Dictionary<string, int> IndexOfAnnot { get; init; } = new()
{
{ string.Empty, 0 },
{ "seal", 0 },
{ "position", 1 },
{ "city", 2 },
{ "date", 3 }
};
/// <summary>
///
/// </summary>
public int DefaultIndexOfAnnot { get; set; } = 0;
/// <summary>
///
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public int GetAnnotationIndex(string name) => IndexOfAnnot.TryGetValue(name, out var value) ? value : DefaultIndexOfAnnot;
}

View File

@@ -82,7 +82,7 @@ public record EnvelopeDto
/// <summary>
///
/// </summary>
public bool UseAccessCode { get; set; } = true;
public bool? UseAccessCode { get; set; }
/// <summary>
///

View File

@@ -36,6 +36,7 @@ public class MappingProfile : Profile
CreateMap<Domain.Entities.Receiver, ReceiverDto>();
CreateMap<Domain.Entities.EnvelopeReceiverReadOnly, EnvelopeReceiverReadOnlyDto>();
CreateMap<ElementAnnotation, AnnotationDto>();
CreateMap<ThirdPartyModule, ThirdPartyModuleDto>();
// DTO to Entity mappings
CreateMap<ConfigDto, Config>();

View File

@@ -0,0 +1,122 @@
using EnvelopeGenerator.Application.Exceptions;
namespace EnvelopeGenerator.Application.Common.Dto.PSPDFKitInstant;
/// <summary>
///
/// </summary>
public class Annotation
{
private string? _id;
/// <summary>
///
/// </summary>
public int EnvelopeId { get; private set; } = 0;
/// <summary>
///
/// </summary>
public int ReceiverId { get; private set; } = 0;
/// <summary>
///
/// </summary>
public int Index { get; private set; } = 0;
/// <summary>
///
/// </summary>
public string EgName { get; private set; } = string.Empty;
/// <summary>
///
/// </summary>
public bool HasStructuredID { get; private set; } = false;
/// <summary>
///
/// </summary>
public bool IsLabel
{
get
{
if (string.IsNullOrEmpty(EgName))
return false;
var parts = EgName.Split('_');
return parts.Length > 1 && parts[1] == "label";
}
}
/// <summary>
///
/// </summary>
public string? Id
{
get => _id;
set
{
_id = value;
if (string.IsNullOrWhiteSpace(value))
throw new BurnAnnotationException("The identifier of annotation is null or empty.");
var parts = value.Split('#');
if (parts.Length != 4)
return;
// throw new BurnAnnotationException($"The identifier of annotation has more or less than 4 sub-part. Id: {_id}");
if (!int.TryParse(parts[0], out int envelopeId))
throw new BurnAnnotationException($"The envelope ID of annotation is not integer. Id: {_id}");
EnvelopeId = envelopeId;
if (!int.TryParse(parts[1], out int receiverId))
throw new BurnAnnotationException($"The receiver ID of annotation is not integer. Id: {_id}");
ReceiverId = receiverId;
if (!int.TryParse(parts[2], out int index))
throw new BurnAnnotationException($"The index of annotation is not integer. Id: {_id}");
Index = index;
EgName = parts[3];
HasStructuredID = true;
}
}
/// <summary>
///
/// </summary>
public List<double>? Bbox { get; set; }
/// <summary>
///
/// </summary>
public string? Type { get; set; }
/// <summary>
///
/// </summary>
public bool IsSignature { get; set; }
/// <summary>
///
/// </summary>
public string? ImageAttachmentId { get; set; }
/// <summary>
///
/// </summary>
public Lines? Lines { get; set; }
/// <summary>
///
/// </summary>
public int PageIndex { get; set; }
/// <summary>
///
/// </summary>
public string? StrokeColor { get; set; }
}

View File

@@ -0,0 +1,8 @@
namespace EnvelopeGenerator.Application.Common.Dto.PSPDFKitInstant;
/// <summary>
///
/// </summary>
/// <param name="Binary"></param>
/// <param name="ContentType"></param>
public record Attachment(string Binary, string ContentType);

View File

@@ -0,0 +1,8 @@
namespace EnvelopeGenerator.Application.Common.Dto.PSPDFKitInstant;
/// <summary>
///
/// </summary>
/// <param name="Name"></param>
/// <param name="Value"></param>
public record FormFieldValue(string Name, string? Value = null);

View File

@@ -0,0 +1,8 @@
namespace EnvelopeGenerator.Application.Common.Dto.PSPDFKitInstant;
/// <summary>
///
/// </summary>
/// <param name="Lines"></param>
/// <param name="StrokeColor"></param>
public record Ink(Lines Lines, string? StrokeColor = null);

View File

@@ -0,0 +1,50 @@
namespace EnvelopeGenerator.Application.Common.Dto.PSPDFKitInstant;
/// <summary>
///
/// </summary>
public class InstantData
{
/// <summary>
///
/// </summary>
public List<Annotation>? Annotations { get; set; }
/// <summary>
///
/// </summary>
public IEnumerable<List<Annotation>>? AnnotationsByReceiver
{
get
{
return Annotations?
.Where(a => a.HasStructuredID)
.GroupBy(a => a.ReceiverId)
.Select(g => g.ToList());
}
}
/// <summary>
///
/// </summary>
public IEnumerable<List<Annotation>>? UnstructuredAnnotations
{
get
{
return Annotations?
.Where(a => !a.HasStructuredID)
.GroupBy(a => a.ReceiverId)
.Select(g => g.ToList());
}
}
/// <summary>
///
/// </summary>
public Dictionary<string, Attachment>? Attachments { get; set; }
/// <summary>
///
/// </summary>
public List<FormFieldValue>? FormFieldValues { get; set; }
}

View File

@@ -0,0 +1,7 @@
namespace EnvelopeGenerator.Application.Common.Dto.PSPDFKitInstant;
/// <summary>
///
/// </summary>
/// <param name="Points"></param>
public record Lines(List<List<List<float>>> Points);

View File

@@ -0,0 +1,57 @@
namespace EnvelopeGenerator.Application.Common.Dto;
/// <summary>
///
/// </summary>
public record ThirdPartyModuleDto
{
/// <summary>
///
/// </summary>
public int Id { get; init; }
/// <summary>
///
/// </summary>
public bool Active { get; init; }
/// <summary>
///
/// </summary>
public string Name { get; init; } = default!;
/// <summary>
///
/// </summary>
public string? Description { get; init; }
/// <summary>
///
/// </summary>
public string License { get; init; } = default!;
/// <summary>
///
/// </summary>
public string Version { get; init; } = default!;
/// <summary>
///
/// </summary>
public string? AddedWho { get; init; }
/// <summary>
///
/// </summary>
public DateTime? AddedWhen { get; init; }
/// <summary>
///
/// </summary>
public string? ChangedWho { get; init; }
/// <summary>
///
/// </summary>
public DateTime? ChangedWhen { get; init; }
}

View File

@@ -0,0 +1,176 @@
using EnvelopeGenerator.Application.Common.Dto.PSPDFKitInstant;
using EnvelopeGenerator.Domain.Constants;
using GdPicture14;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using SixLabors.ImageSharp;
using System.Drawing;
using EnvelopeGenerator.Application.Common.Extensions;
using EnvelopeGenerator.Application.Common.Interfaces.Model;
using EnvelopeGenerator.Application.Common.Configurations;
namespace EnvelopeGenerator.Application.Common.Extensions;
/// <summary>
///
/// </summary>
public static class GdPictureExtensions
{
/// <summary>
///
/// </summary>
/// <param name="manager"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <param name="page"></param>
/// <param name="value"></param>
/// <param name="textStyle"></param>
public static void AddFormFieldValue(this AnnotationManager manager, double x, double y, double width, double height, int page, string value, ITextStyle textStyle)
{
manager.SelectPage(page);
// Add the text annotation
var ant = manager.AddTextAnnot((float)x, (float)y, (float)width, (float)height, value);
// Set the font properties
ant.FontName = textStyle.FontName;
ant.FontSize = textStyle.FontSize;
ant.FontStyle = textStyle.FontStyle;
manager.SaveAnnotationsToPage();
}
/// <summary>
///
/// </summary>
/// <param name="manager"></param>
/// <param name="pAnnotation"></param>
/// <param name="formFieldValue"></param>
/// <param name="options"></param>
public static void AddFormFieldValue(this AnnotationManager manager, Annotation pAnnotation, FormFieldValue formFieldValue, PDFBurnerParams options)
{
var ffIndex = options.GetAnnotationIndex(pAnnotation.EgName);
// Convert pixels to Inches
var oBounds = pAnnotation.Bbox?.Select(points => points.ToInches()).ToList();
if (oBounds is null || oBounds.Count < 4)
return;
double oX = oBounds[0];
double oY = oBounds[1] + options.YOffset * ffIndex + options.TopMargin;
double oWidth = oBounds[2];
double oHeight = oBounds[3];
manager.SelectPage(pAnnotation.PageIndex + 1);
// Add the text annotation
var ant = manager.AddTextAnnot((float)oX, (float)oY, (float)oWidth, (float)oHeight, formFieldValue.Value);
// Set the font properties
ant.FontName = options.FontName;
ant.FontSize = options.FontSize;
ant.FontStyle = options.FontStyle;
manager.SaveAnnotationsToPage();
}
/// <summary>
///
/// </summary>
/// <param name="manager"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <param name="page"></param>
/// <param name="base64"></param>
public static void AddImageAnnotation(this AnnotationManager manager, double x, double y, double width, double height, int page, string base64)
{
manager.SelectPage(page);
manager.AddEmbeddedImageAnnotFromBase64(base64, (float)x, (float)y, (float)width, (float)height);
}
/// <summary>
///
/// </summary>
/// <param name="manager"></param>
/// <param name="pAnnotation"></param>
/// <param name="pAttachments"></param>
public static void AddImageAnnotation(this AnnotationManager manager, Annotation pAnnotation, Dictionary<string, Attachment> pAttachments)
{
var oAttachment = pAttachments
.Where(a => a.Key == pAnnotation.ImageAttachmentId)
.SingleOrDefault();
if (oAttachment.Value == null)
return;
// Convert pixels to Inches
var oBounds = pAnnotation.Bbox?.Select(post => post.ToInches()).ToList();
if (oBounds is null || oBounds.Count < 4)
return;
var oX = oBounds[0];
var oY = oBounds[1];
var oWidth = oBounds[2];
var oHeight = oBounds[3];
manager.SelectPage(pAnnotation.PageIndex + 1);
manager.AddEmbeddedImageAnnotFromBase64(oAttachment.Value.Binary, (float)oX, (float)oY, (float)oWidth, (float)oHeight);
}
/// <summary>
///
/// </summary>
/// <param name="manager"></param>
/// <param name="page"></param>
/// <param name="value"></param>
public static void AddInkAnnotation(this AnnotationManager manager, int page, string value)
{
var ink = JsonConvert.DeserializeObject<Ink>(value);
var oSegments = ink?.Lines.Points;
var oColor = ColorTranslator.FromHtml(ink?.StrokeColor ?? "#000000");
manager.SelectPage(page);
if (oSegments is null)
return;
foreach (var oSegment in oSegments)
{
var oPoints = oSegment
.Select(points => points.ToPointF())
.ToArray();
manager.AddFreeHandAnnot(oColor, oPoints);
}
}
/// <summary>
///
/// </summary>
/// <param name="manager"></param>
/// <param name="pAnnotation"></param>
public static void AddInkAnnotation(this AnnotationManager manager, Annotation pAnnotation)
{
var oSegments = pAnnotation.Lines?.Points;
var oColor = ColorTranslator.FromHtml(pAnnotation.StrokeColor ?? "#000000");
manager.SelectPage(pAnnotation.PageIndex + 1);
if (oSegments is null)
return;
foreach (var oSegment in oSegments)
{
var oPoints = oSegment
.Select(points => points.ToPointF())
.ToArray();
manager.AddFreeHandAnnot(oColor, oPoints);
}
}
}

View File

@@ -0,0 +1,40 @@
using System.Drawing;
namespace EnvelopeGenerator.Application.Common.Extensions;
/// <summary>
///
/// </summary>
public static class MathematExtensions
{
/// <summary>
///
/// </summary>
/// <param name="points"></param>
/// <returns></returns>
public static PointF ToPointF(this List<float> points)
{
var pointsInch = points.Select(ToInches).ToList();
return new PointF(pointsInch[0], pointsInch[1]);
}
/// <summary>
///
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static double ToInches(this double value)
{
return value / 72.0;
}
/// <summary>
///
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static float ToInches(this float value)
{
return value / 72f;
}
}

View File

@@ -0,0 +1,27 @@
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
namespace EnvelopeGenerator.Application.Common.Interfaces.Model;
/// <summary>
///
/// </summary>
public interface ITextStyle
{
/// <summary>
///
/// </summary>
public string FontName { get; set; }
/// <summary>
///
/// </summary>
public int FontSize { get; set; }
/// <summary>
///
/// </summary>
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
public FontStyle FontStyle { get; set; }
}

View File

@@ -0,0 +1,47 @@
using DigitalData.Core.Abstraction.Application.Repository;
using EnvelopeGenerator.Domain.Entities;
using MediatR;
namespace EnvelopeGenerator.Application.Common.Notifications.RemoveSignature.Handlers;
/// <summary>
///
/// </summary>
public class RemoveDocResult : INotificationHandler<RemoveSignatureNotification>
{
private readonly IRepository<Envelope> _repo;
/// <summary>
///
/// </summary>
/// <param name="repository"></param>
public RemoveDocResult(IRepository<Envelope> repository)
{
_repo = repository;
}
/// <summary>
///
/// </summary>
/// <param name="notification"></param>
/// <param name="cancel"></param>
/// <returns></returns>
public Task Handle(RemoveSignatureNotification notification, CancellationToken cancel)
{
if(notification.EnvelopeId is null && notification.EnvelopeUuid is null)
return Task.CompletedTask;
return _repo.UpdateAsync(
envelope => envelope.DocResult = null,
query => {
if (notification.EnvelopeId is int envelopeId)
query = query.Where(envelope => envelope.Id == envelopeId);
if (notification.EnvelopeUuid is string uuid)
query = query.Where(envelope => envelope.Uuid == uuid);
return query;
}, cancel);
}
}

View File

@@ -2,11 +2,15 @@
using EnvelopeGenerator.Application.Common.Configurations;
using EnvelopeGenerator.Application.Common.Interfaces.Services;
using EnvelopeGenerator.Application.Services;
using EnvelopeGenerator.Application.ThirdPartyModules.Queries;
using MediatR;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using QRCoder;
using System.Reflection;
using GdPicture14;
namespace EnvelopeGenerator.Application;
@@ -46,6 +50,28 @@ public static class DependencyInjection
services.Configure<MailParams>(config.GetSection(nameof(MailParams)));
services.Configure<AuthenticatorParams>(config.GetSection(nameof(AuthenticatorParams)));
services.Configure<TotpSmsParams>(config.GetSection(nameof(TotpSmsParams)));
services.Configure<PDFBurnerParams>(config.GetSection(nameof(PDFBurnerParams)));
services.AddOptions<GdPictureParams>()
.Configure((GdPictureParams opt, IServiceProvider sp) =>
{
var licenseKey = "GDPICTURE";
using var scope = sp.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
opt.License = config["GdPictureLicenseKey"]
?? mediator.ReadThirdPartyModuleLicenseAsync(licenseKey).GetAwaiter().GetResult()
?? throw new InvalidOperationException($"License record not found for key: {licenseKey}");
});
services.AddSingleton(provider => {
var license = provider.GetRequiredService<IOptions<GdPictureParams>>().Value.License;
var licenseManager = new LicenseManager();
licenseManager.RegisterKEY(license);
return licenseManager;
});
services.AddTransient(provider => {
// Ensure LicenseManager is resolved so that its constructor is called
_ = provider.GetRequiredService<LicenseManager>();
return new AnnotationManager();
});
services.AddHttpClientService<GtxMessagingParams>(config.GetSection(nameof(GtxMessagingParams)));
services.TryAddSingleton<ISmsSender, GTXSmsSender>();
@@ -58,6 +84,12 @@ public static class DependencyInjection
cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
});
// Add memory cache
services.AddMemoryCache();
// Register mail services
services.AddScoped<IEnvelopeMailService, EnvelopeMailService>();
return services;
}
}

View File

@@ -14,7 +14,7 @@
<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.4.0" />
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.5.0" />
<PackageReference Include="DigitalData.Core.Application" Version="3.4.0" />
<PackageReference Include="DigitalData.Core.Client" Version="2.1.0" />
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" />
@@ -25,23 +25,94 @@
<PackageReference Include="Otp.NET" Version="1.4.0" />
<PackageReference Include="QRCoder" Version="1.6.0" />
<PackageReference Include="QRCoder-ImageSharp" Version="0.10.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
<PackageReference Include="UserManager" Version="1.1.3" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="System.Formats.Asn1" Version="8.0.2" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
<PackageReference Include="System.Formats.Asn1" Version="9.0.10" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
<PackageReference Include="System.Formats.Asn1" Version="9.0.10" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
<ProjectReference Include="..\EnvelopeGenerator.PdfEditor\EnvelopeGenerator.PdfEditor.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="GdPicture.NET.14">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.barcode.1d.writer">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.barcode.1d.writer.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.barcode.2d.writer">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.barcode.2d.writer.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.CAD">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.CAD.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.CAD.DWG">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.CAD.DWG.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.Common">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.Common.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.Document">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.Document.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.Email">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.Email.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.HTML">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.HTML.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.Imaging">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.Imaging.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.Imaging.Formats">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.Imaging.Formats.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.Imaging.Formats.Conversion">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.Imaging.Formats.Conversion.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.Imaging.Rendering">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.Imaging.Rendering.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.MSOfficeBinary">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.MSOfficeBinary.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.OpenDocument">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.OpenDocument.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.OpenXML">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.OpenXML.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.OpenXML.Templating">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.OpenXML.Templating.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.PDF">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.PDF.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.RTF">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.RTF.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.SVG">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.SVG.dll</HintPath>
</Reference>
<Reference Include="GdPicture.NET.14.wia.gateway">
<HintPath>..\packages\GdPicture.14.3.3\lib\net462\GdPicture.NET.14.wia.gateway.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>

View File

@@ -1,18 +1,29 @@
using MediatR;
using EnvelopeGenerator.Domain.Constants;
using EnvelopeGenerator.Application.Common.Query;
using EnvelopeGenerator.Application.Common.Dto;
using AutoMapper;
using DigitalData.Core.Abstraction.Application.Repository;
using EnvelopeGenerator.Application.Common.Extensions;
using EnvelopeGenerator.Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace EnvelopeGenerator.Application.Envelopes.Queries;
/// <summary>
/// Repräsentiert eine Abfrage für Umschläge.
/// </summary>
public record ReadEnvelopeQuery : EnvelopeQueryBase, IRequest
public record ReadEnvelopeQuery : EnvelopeQueryBase, IRequest<IEnumerable<EnvelopeDto>>
{
/// <summary>
/// Abfrage des Include des Umschlags
/// </summary>
public EnvelopeStatusQuery? Status { get; init; }
/// <summary>
///
/// </summary>
public bool? HasDocResult { get; init; }
}
/// <summary>
@@ -65,4 +76,53 @@ public record EnvelopeStatusQuery
/// Eine Liste von Statuswerten, die ignoriert werden werden.
/// </summary>
public EnvelopeStatus[]? Ignore { get; init; }
}
/// <summary>
///
/// </summary>
public class ReadEnvelopeQueryHandler : IRequestHandler<ReadEnvelopeQuery, IEnumerable<EnvelopeDto>>
{
private readonly IMapper _mapper;
private readonly IRepository<Envelope> _repo;
/// <summary>
///
/// </summary>
/// <param name="mapper"></param>
/// <param name="repo"></param>
public ReadEnvelopeQueryHandler(IMapper mapper, IRepository<Envelope> repo)
{
_mapper = mapper;
_repo = repo;
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancel"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<IEnumerable<EnvelopeDto>> Handle(ReadEnvelopeQuery request, CancellationToken cancel)
{
var envelopesQ = _repo.Query.Where(request, notnull: false);
EnvelopeStatusQuery? statusQ = request.Status;
bool? hasDocResult = request.HasDocResult;
var filtered =
from envelope in envelopesQ
where (statusQ == null || statusQ.Include == null || statusQ.Include.Contains(envelope.Status))
&& (statusQ == null || statusQ.Ignore == null || !statusQ.Ignore.Contains(envelope.Status))
&& (statusQ == null || statusQ.Min == null || envelope.Status > statusQ.Min)
&& (statusQ == null || statusQ.Max == null || envelope.Status < statusQ.Max)
&& (!hasDocResult.HasValue || (hasDocResult.Value ? envelope.DocResult != null : envelope.DocResult == null))
select envelope;
var envelopes = await filtered.ToListAsync(cancel);
return _mapper.Map<IEnumerable<EnvelopeDto>>(envelopes);
}
}

View File

@@ -0,0 +1,19 @@
namespace EnvelopeGenerator.Application.Exceptions;
/// <summary>
///
/// </summary>
public class BurnAnnotationException : ApplicationException
{
/// <summary>
///
/// </summary>
public BurnAnnotationException(string message)
: base(message) { }
/// <summary>
///
/// </summary>
public BurnAnnotationException(string message, Exception innerException)
: base(message, innerException) { }
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EnvelopeGenerator.Application.Exceptions;
/// <summary>
///
/// </summary>
public class CreateReportException : ApplicationException
{
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public CreateReportException(string message)
: base(message) { }
/// <summary>
///
/// </summary>
/// <param name="message"></param>
/// <param name="innerException"></param>
public CreateReportException(string message, Exception innerException)
: base(message, innerException) { }
}

View File

@@ -0,0 +1,22 @@
namespace EnvelopeGenerator.Application.Exceptions;
/// <summary>
///
/// </summary>
public class ExportDocumentException : ApplicationException
{
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public ExportDocumentException(string message)
: base(message) { }
/// <summary>
///
/// </summary>
/// <param name="message"></param>
/// <param name="innerException"></param>
public ExportDocumentException(string message, Exception innerException)
: base(message, innerException) { }
}

View File

@@ -0,0 +1,22 @@
namespace EnvelopeGenerator.Application.Exceptions;
/// <summary>
///
/// </summary>
public class MergeDocumentException : ApplicationException
{
/// <summary>
///
/// </summary>
/// <param name="message"></param>
public MergeDocumentException(string message)
: base(message) { }
/// <summary>
///
/// </summary>
/// <param name="message"></param>
/// <param name="innerException"></param>
public MergeDocumentException(string message, Exception innerException)
: base(message, innerException) { }
}

View File

@@ -1,101 +0,0 @@
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using EnvelopeGenerator.Domain.Constants;
using EnvelopeGenerator.Domain.Entities;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace EnvelopeGenerator.Application.Histories.Queries;
//TODO: Add sender query
/// <summary>
/// Repräsentiert eine Abfrage für die Verlaufshistorie eines Umschlags.
/// </summary>
public record CountHistoryQuery : HistoryQueryBase, IRequest<int>;
/// <summary>
///
/// </summary>
public static class CountHistoryQueryExtensions
{
/// <summary>
///
/// </summary>
/// <param name="sender"></param>
/// <param name="uuid"></param>
/// <param name="statuses"></param>
/// <param name="cancel"></param>
/// <returns></returns>
public static async Task<bool> AnyHistoryAsync(this ISender sender, string uuid, IEnumerable<EnvelopeStatus> statuses, CancellationToken cancel = default)
{
var count = await sender.Send(new CountHistoryQuery
{
Envelope = new() { Uuid = uuid },
Statuses = new() { Include = statuses }
}, cancel);
return count > 0;
}
}
/// <summary>
///
/// </summary>
public class CountHistoryQueryHandler : IRequestHandler<CountHistoryQuery, int>
{
private readonly IRepository<History> _repo;
/// <summary>
///
/// </summary>
/// <param name="repo"></param>
public CountHistoryQueryHandler(IRepository<History> repo)
{
_repo = repo;
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancel"></param>
/// <returns></returns>
/// <exception cref="NotFoundException"></exception>
public Task<int> Handle(CountHistoryQuery request, CancellationToken cancel = default)
{
var query = _repo.Query;
if (request.Envelope.Id is int envId)
query = query.Where(e => e.Id == envId);
else if (request.Envelope.Uuid is string uuid)
query = query.Where(e => e.Envelope!.Uuid == uuid);
#pragma warning disable CS0618 // Type or member is obsolete
else if (request.EnvelopeId is not null)
query = query.Where(h => h.EnvelopeId == request.EnvelopeId);
#pragma warning restore CS0618 // Type or member is obsolete
else
throw new BadRequestException("Invalid request: An Envelope object or a valid EnvelopeId/UUID must be supplied.");
#pragma warning disable CS0618 // Type or member is obsolete
if (request.Status is not null)
query = query.Where(h => h.Status == request.Status);
#pragma warning restore CS0618 // Type or member is obsolete
if (request.Statuses is not null)
{
var status = request.Statuses;
if (status.Min is not null)
query = query.Where(er => er.Envelope!.Status >= status.Min);
if (status.Max is not null)
query = query.Where(er => er.Envelope!.Status <= status.Max);
if (status.Include?.Count() > 0)
query = query.Where(er => status.Include.Contains(er.Envelope!.Status));
if (status.Ignore is not null)
query = query.Where(er => !status.Ignore.Contains(er.Envelope!.Status));
}
return query.CountAsync(cancel);
}
}

View File

@@ -1,60 +0,0 @@
using EnvelopeGenerator.Application.Common.Query;
using EnvelopeGenerator.Domain.Constants;
using System.ComponentModel.DataAnnotations;
namespace EnvelopeGenerator.Application.Histories.Queries;
//TODO: Add sender query
/// <summary>
///
/// </summary>
public record HistoryQueryBase
{
/// <summary>
/// Die eindeutige Kennung des Umschlags.
/// </summary>
[Obsolete("Use Envelope property")]
public int? EnvelopeId { get; set; }
/// <summary>
/// Der Include des Umschlags, der abgefragt werden soll. Kann optional angegeben werden, um die Ergebnisse zu filtern.
/// </summary>
[Obsolete("Use statuses")]
public EnvelopeStatus? Status { get; set; }
/// <summary>
///
/// </summary>
public EnvelopeStatusQuery Statuses { get; set; } = new();
/// <summary>
///
/// </summary>
public EnvelopeQueryBase Envelope { get; set; } = new EnvelopeQueryBase();
}
/// <summary>
///
/// </summary>
public record EnvelopeStatusQuery
{
/// <summary>
/// Der minimale Statuswert, der berücksichtigt werden.
/// </summary>
public EnvelopeStatus? Min { get; init; }
/// <summary>
/// Der maximale Statuswert, der berücksichtigt werden.
/// </summary>
public EnvelopeStatus? Max { get; init; }
/// <summary>
/// Eine Liste von Statuswerten, die einbezogen werden.
/// </summary>
public IEnumerable<EnvelopeStatus>? Include { get; init; }
/// <summary>
/// Eine Liste von Statuswerten, die ignoriert werden werden.
/// </summary>
public IEnumerable<EnvelopeStatus>? Ignore { get; init; }
}

View File

@@ -1,10 +1,7 @@
using AutoMapper;
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using EnvelopeGenerator.Application.Common.Dto.History;
using EnvelopeGenerator.Application.Common.Dto.History;
using EnvelopeGenerator.Domain.Constants;
using MediatR;
using EnvelopeGenerator.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;
namespace EnvelopeGenerator.Application.Histories.Queries;
@@ -12,81 +9,21 @@ namespace EnvelopeGenerator.Application.Histories.Queries;
/// <summary>
/// Repräsentiert eine Abfrage für die Verlaufshistorie eines Umschlags.
/// </summary>
public record ReadHistoryQuery : HistoryQueryBase, IRequest<IEnumerable<HistoryDto>>
public record ReadHistoryQuery : IRequest<IEnumerable<HistoryDto>>
{
/// <summary>
/// Die eindeutige Kennung des Umschlags.
/// </summary>
[Required]
public int EnvelopeId { get; init; }
/// <summary>
/// Der Include des Umschlags, der abgefragt werden soll. Kann optional angegeben werden, um die Ergebnisse zu filtern.
/// </summary>
public EnvelopeStatus? Status { get; init; }
/// <summary>
/// Abfrage zur Steuerung, ob nur der aktuelle Include oder der gesamte Datensatz zurückgegeben wird.
/// </summary>
public bool OnlyLast { get; init; } = true;
}
/// <summary>
///
/// </summary>
public class ReadHistoryQueryHandler : IRequestHandler<ReadHistoryQuery, IEnumerable<HistoryDto>>
{
private readonly IRepository<History> _repo;
private readonly IMapper _mapper;
/// <summary>
///
/// </summary>
/// <param name="repo"></param>
/// <param name="mapper"></param>
public ReadHistoryQueryHandler(IRepository<History> repo, IMapper mapper)
{
_repo = repo;
_mapper = mapper;
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancel"></param>
/// <returns></returns>
/// <exception cref="NotFoundException"></exception>
public async Task<IEnumerable<HistoryDto>> Handle(ReadHistoryQuery request, CancellationToken cancel = default)
{
var query = _repo.Query;
if (request.Envelope.Id is int envId)
query = query.Where(e => e.Id == envId);
else if (request.Envelope.Uuid is string uuid)
query = query.Where(e => e.Envelope!.Uuid == uuid);
#pragma warning disable CS0618 // Type or member is obsolete
else if (request.EnvelopeId is not null)
query = query.Where(h => h.EnvelopeId == request.EnvelopeId);
#pragma warning restore CS0618 // Type or member is obsolete
else
throw new BadRequestException("Invalid request: An Envelope object or a valid EnvelopeId/UUID must be supplied.");
#pragma warning disable CS0618 // Type or member is obsolete
if (request.Status is not null)
query = query.Where(h => h.Status == request.Status);
#pragma warning restore CS0618 // Type or member is obsolete
if (request.Statuses is not null)
{
var status = request.Statuses;
if (status.Min is not null)
query = query.Where(er => er.Envelope!.Status >= status.Min);
if (status.Max is not null)
query = query.Where(er => er.Envelope!.Status <= status.Max);
if (status.Include?.Count() > 0)
query = query.Where(er => status.Include.Contains(er.Envelope!.Status));
if (status.Ignore is not null)
query = query.Where(er => !status.Ignore.Contains(er.Envelope!.Status));
}
if (request.OnlyLast)
query = query.OrderByDescending(x => x.AddedWhen);
var hists = await query.ToListAsync(cancel);
return _mapper.Map<List<HistoryDto>>(hists);
}
public bool? OnlyLast { get; init; } = true;
}

View File

@@ -0,0 +1,47 @@
using AutoMapper;
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.Core.Exceptions;
using EnvelopeGenerator.Application.Common.Dto.History;
using EnvelopeGenerator.Domain.Entities;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace EnvelopeGenerator.Application.Histories.Queries;
/// <summary>
///
/// </summary>
public class ReadHistoryQueryHandler : IRequestHandler<ReadHistoryQuery, IEnumerable<HistoryDto>>
{
private readonly IRepository<History> _repo;
private readonly IMapper _mapper;
/// <summary>
///
/// </summary>
/// <param name="repo"></param>
/// <param name="mapper"></param>
public ReadHistoryQueryHandler(IRepository<History> repo, IMapper mapper)
{
_repo = repo;
_mapper = mapper;
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancel"></param>
/// <returns></returns>
/// <exception cref="NotFoundException"></exception>
public async Task<IEnumerable<HistoryDto>> Handle(ReadHistoryQuery request, CancellationToken cancel = default)
{
var query = _repo.ReadOnly().Where(h => h.EnvelopeId == request.EnvelopeId);
if (request.Status is not null)
query = query.Where(h => h.Status == request.Status);
var hists = await query.ToListAsync(cancel);
return _mapper.Map<List<HistoryDto>>(hists);
}
}

View File

@@ -0,0 +1,241 @@
using DigitalData.Core.Abstraction.Application.Repository;
using EnvelopeGenerator.Application.Common.Configurations;
using EnvelopeGenerator.Application.Exceptions;
using EnvelopeGenerator.Application.Common.Dto.PSPDFKitInstant;
using EnvelopeGenerator.Domain.Constants;
using EnvelopeGenerator.Domain.Entities;
using GdPicture14;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using EnvelopeGenerator.Application.Common.Extensions;
namespace EnvelopeGenerator.Application.Pdf;
/// <summary>
///
/// </summary>
public record BurnPdfCommand(byte[] Document, List<string> InstantJSONList, int EnvelopeId) : IRequest<byte[]>;
/// <summary>
///
/// </summary>
public class BurnPdfCommandHandler : IRequestHandler<BurnPdfCommand, byte[]>
{
private readonly PDFBurnerParams _options;
private readonly AnnotationManager _manager;
private readonly IRepository<Signature> _signRepo;
private readonly ILogger<BurnPdfCommandHandler> _logger;
/// <summary>
///
/// </summary>
/// <param name="pdfBurnerParams"></param>
/// <param name="manager"></param>
/// <param name="signRepo"></param>
/// <param name="logger"></param>
public BurnPdfCommandHandler(IOptions<PDFBurnerParams> pdfBurnerParams, AnnotationManager manager, IRepository<Signature> signRepo, ILogger<BurnPdfCommandHandler> logger)
{
_options = pdfBurnerParams.Value;
_manager = manager;
_signRepo = signRepo;
_logger = logger;
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancel"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<byte[]> Handle(BurnPdfCommand request, CancellationToken cancel)
{
// read the elements of envelope with their annotations
var elements = await _signRepo
.Where(sig => sig.Document.EnvelopeId == request.EnvelopeId)
.Include(sig => sig.Annotations)
.ToListAsync(cancel);
return elements.Count > 0
? BurnElementAnnotsToPDF(request.Document, elements)
: BurnInstantJSONAnnotsToPDF(request.Document, request.InstantJSONList);
}
private byte[] BurnElementAnnotsToPDF(byte[] pSourceBuffer, List<Signature> elements)
{
// Add background
using var doc = PdfEditor.Pdf.FromMemory(pSourceBuffer);
// TODO: take the length from the largest y
pSourceBuffer = doc.Background(elements, 1.9500000000000002 * 0.93, 2.52 * 0.67)
.ExportStream()
.ToArray();
GdPictureStatus oResult;
using var oSourceStream = new MemoryStream(pSourceBuffer);
// Open PDF
oResult = _manager.InitFromStream(oSourceStream);
if (oResult != GdPictureStatus.OK)
throw new BurnAnnotationException($"Could not open document for burning: [{oResult}]");
// Imported from background (add to configuration)
var margin = 0.2;
var inchFactor = 72;
// Y offset of form fields
var keys = new[] { "position", "city", "date" }; // add to configuration
var unitYOffsets = 0.2;
var yOffsetsOfFF = keys
.Select((k, i) => new { Key = k, Value = unitYOffsets * i + 1 })
.ToDictionary(x => x.Key, x => x.Value);
// Add annotations
foreach (var element in elements)
{
var frameX = element.Left - 0.7 - margin;
var frame = element.Annotations?.FirstOrDefault(a => a.Name == "frame");
var frameY = element.Top - 0.5 - margin;
var frameYShift = (frame!.Y ?? default) - frameY * inchFactor;
var frameXShift = (frame.X ?? default) - frameX * inchFactor;
foreach (var annot in element.Annotations!)
{
if (!yOffsetsOfFF.TryGetValue(annot.Name, out var yOffsetofFF))
yOffsetofFF = 0;
var y = frameY + yOffsetofFF;
if (annot.Type == AnnotationType.PSPDFKit.FormField)
{
_manager.AddFormFieldValue(
(annot.X ?? default) / inchFactor,
y,
(annot.Width ?? default) / inchFactor,
(annot.Height ?? default) / inchFactor,
element.Page,
annot.Value,
_options
);
}
else if (annot.Type == AnnotationType.PSPDFKit.Image)
{
_manager.AddImageAnnotation(
(annot.X ?? default) / inchFactor,
annot.Name == "signature" ? ((annot.Y ?? default) - frameYShift) / inchFactor : y,
(annot.Width ?? default) / inchFactor,
(annot.Height ?? default) / inchFactor,
element.Page,
annot.Value
);
}
else if (annot.Type == AnnotationType.PSPDFKit.Ink)
{
_manager.AddInkAnnotation(element.Page, annot.Value);
}
}
}
// Save PDF
using var oNewStream = new MemoryStream();
oResult = _manager.SaveDocumentToPDF(oNewStream);
if (oResult != GdPictureStatus.OK)
throw new BurnAnnotationException($"Could not save document to stream: [{oResult}]");
_manager.Close();
return oNewStream.ToArray();
}
private byte[] BurnInstantJSONAnnotsToPDF(byte[] pSourceBuffer, List<string> pInstantJSONList)
{
GdPictureStatus oResult;
using var oSourceStream = new MemoryStream(pSourceBuffer);
// Open PDF
oResult = _manager.InitFromStream(oSourceStream);
if (oResult != GdPictureStatus.OK)
{
throw new BurnAnnotationException($"Could not open document for burning: [{oResult}]");
}
// Add annotation to PDF
foreach (var oJSON in pInstantJSONList)
{
try
{
if (oJSON is string json)
AddInstantJsonAnnotationToPdf(json);
}
catch (Exception ex)
{
_logger.LogWarning("Error in AddInstantJSONAnnotationToPDF - oJson: ");
_logger.LogWarning(oJSON);
throw new BurnAnnotationException("Adding Annotation failed", ex);
}
}
oResult = _manager.BurnAnnotationsToPage(RemoveInitialAnnots: true, VectorMode: true);
if (oResult != GdPictureStatus.OK)
{
throw new BurnAnnotationException($"Could not burn annotations to file: [{oResult}]");
}
// Save PDF
using var oNewStream = new MemoryStream();
oResult = _manager.SaveDocumentToPDF(oNewStream);
if (oResult != GdPictureStatus.OK)
{
throw new BurnAnnotationException($"Could not save document to stream: [{oResult}]");
}
_manager.Close();
return oNewStream.ToArray();
}
private void AddInstantJsonAnnotationToPdf(string instantJson)
{
var annotationData = JsonConvert.DeserializeObject<InstantData>(instantJson);
annotationData?.Annotations?.Reverse();
if (annotationData?.Annotations is null)
return;
foreach (var annotation in annotationData.Annotations)
{
_logger.LogDebug("Adding AnnotationID: {id}", annotation.Id);
switch (annotation.Type)
{
case AnnotationType.PSPDFKit.Image:
if (annotationData?.Attachments is not null)
_manager.AddImageAnnotation(annotation, annotationData.Attachments);
break;
case AnnotationType.PSPDFKit.Ink:
_manager.AddInkAnnotation(annotation);
break;
case AnnotationType.PSPDFKit.Widget:
// Add form field values
var formFieldValue = annotationData?.FormFieldValues?
.FirstOrDefault(fv => fv.Name == annotation.Id);
if (formFieldValue != null && !_options.IgnoredLabels.Contains(formFieldValue.Value))
{
_manager.AddFormFieldValue(annotation, formFieldValue, _options);
}
break;
}
}
}
}

View File

@@ -0,0 +1,92 @@
using AutoMapper;
using DigitalData.Core.Abstraction.Application.Repository;
using EnvelopeGenerator.Application.Common.Dto;
using EnvelopeGenerator.Domain.Entities;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace EnvelopeGenerator.Application.ThirdPartyModules.Queries;
/// <summary>
///
/// </summary>
public record ReadThirdPartyModuleQuery : IRequest<IEnumerable<ThirdPartyModuleDto>>
{
/// <summary>
///
/// </summary>
public string? Name { get; init; }
/// <summary>
///
/// </summary>
public bool? Active { get; init; }
}
/// <summary>
///
/// </summary>
public record ReadThirdPartyModuleQueryHandler : IRequestHandler<ReadThirdPartyModuleQuery, IEnumerable<ThirdPartyModuleDto>>
{
private readonly IMapper _mapper;
private readonly IRepository<ThirdPartyModule> _repo;
/// <summary>
///
/// </summary>
/// <param name="mapper"></param>
/// <param name="repo"></param>
public ReadThirdPartyModuleQueryHandler(IMapper mapper, IRepository<ThirdPartyModule> repo)
{
_mapper = mapper;
_repo = repo;
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancel"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<IEnumerable<ThirdPartyModuleDto>> Handle(ReadThirdPartyModuleQuery request, CancellationToken cancel)
{
var query = _repo.Query;
if(request.Name is string name)
query = query.Where(m => m.Name == name);
if (request.Active is bool active)
query = query.Where(m => m.Active == active);
var modules = await query.ToListAsync(cancel);
return _mapper.Map<IEnumerable<ThirdPartyModuleDto>>(modules);
}
}
/// <summary>
///
/// </summary>
public static class ReadThirdPartyModuleQueryExtensions
{
/// <summary>
///
/// </summary>
/// <param name="mediator"></param>
/// <param name="name"></param>
/// <param name="active"></param>
/// <param name="cancel"></param>
/// <returns></returns>
public static async Task<string?> ReadThirdPartyModuleLicenseAsync(this IMediator mediator, string name, bool active = true, CancellationToken cancel = default)
{
var modules = await mediator.Send(new ReadThirdPartyModuleQuery()
{
Name = name,
Active = active,
}, cancel);
return modules.FirstOrDefault()?.License;
}
}

View File

@@ -70,8 +70,8 @@
<Reference Include="DigitalData.Controls.DocumentViewer, Version=1.9.8.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DigitalData.Controls.DocumentViewer.1.9.8\lib\net462\DigitalData.Controls.DocumentViewer.dll</HintPath>
</Reference>
<Reference Include="DigitalData.Core.Abstraction.Application, Version=1.4.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DigitalData.Core.Abstraction.Application.1.4.0\lib\net462\DigitalData.Core.Abstraction.Application.dll</HintPath>
<Reference Include="DigitalData.Core.Abstraction.Application, Version=1.5.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DigitalData.Core.Abstraction.Application.1.5.0\lib\net462\DigitalData.Core.Abstraction.Application.dll</HintPath>
</Reference>
<Reference Include="DigitalData.Core.Abstractions, Version=4.3.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DigitalData.Core.Abstractions.4.3.0\lib\net462\DigitalData.Core.Abstractions.dll</HintPath>

View File

@@ -32,7 +32,7 @@ Public Class frmFinalizePDF
#Disable Warning BC40000 ' Type or member is obsolete
Factory.Shared _
.BehaveOnPostBuild(PostBuildBehavior.Ignore) _
.AddEnvelopeGeneratorInfrastructureServices(
.AddEGInfrastructureServices(
Sub(opt)
opt.AddDbTriggerParams(
Sub(triggers)

View File

@@ -3,7 +3,7 @@
<package id="AutoMapper" version="10.1.1" targetFramework="net462" />
<package id="BouncyCastle.Cryptography" version="2.5.0" targetFramework="net462" />
<package id="DigitalData.Controls.DocumentViewer" version="1.9.8" targetFramework="net462" />
<package id="DigitalData.Core.Abstraction.Application" version="1.4.0" targetFramework="net462" />
<package id="DigitalData.Core.Abstraction.Application" version="1.5.0" targetFramework="net462" />
<package id="DigitalData.Core.Abstractions" version="4.3.0" targetFramework="net462" />
<package id="DigitalData.Modules.Base" version="1.3.8" targetFramework="net462" />
<package id="DigitalData.Modules.Config" version="1.3.0" targetFramework="net462" />

View File

@@ -72,8 +72,8 @@
<Reference Include="DevExpress.XtraEditors.v21.2, Version=21.2.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a" />
<Reference Include="DevExpress.XtraGauges.v21.2.Core, Version=21.2.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a, processorArchitecture=MSIL" />
<Reference Include="DevExpress.XtraReports.v21.2, Version=21.2.4.0, Culture=neutral, PublicKeyToken=b88d1754d700e49a, processorArchitecture=MSIL" />
<Reference Include="DigitalData.Core.Abstraction.Application, Version=1.4.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DigitalData.Core.Abstraction.Application.1.4.0\lib\net462\DigitalData.Core.Abstraction.Application.dll</HintPath>
<Reference Include="DigitalData.Core.Abstraction.Application, Version=1.5.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DigitalData.Core.Abstraction.Application.1.5.0\lib\net462\DigitalData.Core.Abstraction.Application.dll</HintPath>
</Reference>
<Reference Include="DigitalData.Core.Abstractions, Version=4.3.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DigitalData.Core.Abstractions.4.3.0\lib\net462\DigitalData.Core.Abstractions.dll</HintPath>

View File

@@ -72,7 +72,7 @@ Namespace Jobs
#Disable Warning BC40000 ' Type or member is obsolete
Factory.Shared _
.BehaveOnPostBuild(PostBuildBehavior.Ignore) _
.AddEnvelopeGeneratorInfrastructureServices(
.AddEGInfrastructureServices(
Sub(opt)
opt.AddDbTriggerParams(
Sub(triggers)

View File

@@ -2,7 +2,7 @@
<packages>
<package id="AutoMapper" version="10.1.1" targetFramework="net462" />
<package id="BouncyCastle.Cryptography" version="2.5.0" targetFramework="net462" />
<package id="DigitalData.Core.Abstraction.Application" version="1.4.0" targetFramework="net462" />
<package id="DigitalData.Core.Abstraction.Application" version="1.5.0" targetFramework="net462" />
<package id="DigitalData.Core.Abstractions" version="4.3.0" targetFramework="net462" />
<package id="DigitalData.Modules.Base" version="1.3.8" targetFramework="net462" />
<package id="DigitalData.Modules.Config" version="1.3.0" targetFramework="net462" />

View File

@@ -0,0 +1,98 @@
using DigitalData.EmailProfilerDispatcher;
using DigitalData.UserManager.DependencyInjection;
using EnvelopeGenerator.Application;
using EnvelopeGenerator.Infrastructure;
using Microsoft.Extensions.Caching.SqlServer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using static EnvelopeGenerator.Infrastructure.DependencyInjection;
namespace EnvelopeGenerator.DependencyInjection;
public static class DependencyInjection
{
public static IServiceCollection AddEnvelopeGenerator(this IServiceCollection services, Action<EGConfiguration> options)
{
var egConfig = new EGConfiguration();
options.Invoke(egConfig);
egConfig.EnsureAllServicesConfigured();
egConfig.RegisterAll(services);
// Add envelope generator services
#pragma warning disable CS0618
services.AddUserManager<EGDbContext>();
#pragma warning restore CS0618
services.AddDispatcher<EGDbContext>();
return services;
}
public record EGConfiguration
{
internal readonly Queue<Action<IServiceCollection>> _serviceRegs = new();
internal void RegisterAll(IServiceCollection services)
{
while (_serviceRegs.Count > 0)
_serviceRegs.Dequeue().Invoke(services);
}
// TODO: update to use attributes and reflections instead of _addingStatus-dictionary
private readonly Dictionary<string, bool> _addingStatus = new ()
{
{ nameof(AddLocalization), false },
{ nameof(AddDistributedSqlServerCache), false },
{ nameof(AddInfrastructure), false },
{ nameof(AddServices), false },
};
public EGConfiguration AddLocalization(Action<IServiceCollection>? customLocalizationOptions = null)
{
_serviceRegs.Enqueue(customLocalizationOptions ?? (s => s.AddLocalization()));
_addingStatus[nameof(AddLocalization)] = true;
return this;
}
public EGConfiguration AddDistributedSqlServerCache(Action<SqlServerCacheOptions> setupAction)
{
_serviceRegs.Enqueue(s => s.AddDistributedSqlServerCache(setupAction));
_addingStatus[nameof(AddDistributedSqlServerCache)] = true;
return this;
}
public EGConfiguration AddInfrastructure(Action<EGInfrastructureConfiguration> options)
{
#pragma warning disable CS0618
_serviceRegs.Enqueue(s => s.AddEGInfrastructureServices(options));
#pragma warning restore CS0618
_addingStatus[nameof(AddInfrastructure)] = true;
return this;
}
public EGConfiguration AddServices(IConfiguration config)
{
#pragma warning disable CS0618
_serviceRegs.Enqueue(s => s.AddEnvelopeGeneratorServices(config));
#pragma warning restore CS0618
_addingStatus[nameof(AddServices)] = true;
return this;
}
internal void EnsureAllServicesConfigured()
{
var missingServices = _addingStatus
.Where(kv => !kv.Value)
.Select(kv => kv.Key)
.ToList();
if (missingServices.Count > 0)
{
var missingList = string.Join(", ", missingServices);
throw new InvalidOperationException(
$"Service configuration incomplete. The following required service methods were not called: {missingList}. " +
"Please ensure all necessary configuration methods are invoked before building the application.");
}
}
}
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.10" />
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="9.0.10" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.10" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj" />
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
namespace EnvelopeGenerator.Domain.Constants
{
public static class AnnotationType
{
public static class PSPDFKit
{
public const string Image = "pspdfkit/image";
public const string Ink = "pspdfkit/ink";
public const string Widget = "pspdfkit/widget";
public const string FormField = "pspdfkit/form-field-value";
}
}
}

View File

@@ -2,7 +2,6 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using EnvelopeGenerator.Domain.Constants;
#if NETFRAMEWORK
using System;
using System.Collections.Generic;
@@ -134,14 +133,12 @@ public class Envelope
= false;
#endif
[NotMapped]
[Column("DOC_RESULT")]
public byte[]
#if NET
?
#endif
DocResult
{ get; set; }
DocResult { get; set; }
[ForeignKey("EnvelopeTypeId")]
public virtual EnvelopeType

View File

@@ -5,6 +5,8 @@ using System.ComponentModel.DataAnnotations.Schema;
#if NETFRAMEWORK
using System;
using System.Collections.Generic;
#elif NET
using System.Text.Json.Serialization;
#endif
namespace EnvelopeGenerator.Domain.Entities
@@ -120,13 +122,17 @@ public class Signature : ISignature, IHasReceiver
#endif
Annotations { get; set; }
#if NETFRAMEWORK
#if NET
[JsonIgnore]
#endif
[NotMapped]
public double Top => Math.Round(Y, 5);
#if NET
[JsonIgnore]
#endif
[NotMapped]
public double Left => Math.Round(X, 5);
#endif
}
#if NETFRAMEWORK

View File

@@ -0,0 +1,73 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
#if NETFRAMEWORK
using System;
#endif
namespace EnvelopeGenerator.Domain.Entities
{
[Table("TBDD_3RD_PARTY_MODULES")]
public class ThirdPartyModule
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("GUID")]
public int Id { get; set; }
[Required]
[Column("ACTIVE")]
public bool Active { get; set; }
[Required]
[StringLength(50)]
[Column("NAME")]
public string Name { get; set; }
[StringLength(500)]
[Column("DESCRIPTION")]
public string
#if nullable
?
#endif
Description { get; set; }
[Required]
[Column("LICENSE", TypeName = "varchar(max)")]
public string License { get; set; }
[Required]
[StringLength(20)]
[Column("VERSION")]
public string Version { get; set; }
[StringLength(50)]
[Column("ADDED_WHO")]
public string
#if nullable
?
#endif
AddedWho { get; set; }
[Column("ADDED_WHEN")]
public DateTime
#if nullable
?
#endif
AddedWhen { get; set; }
[StringLength(50)]
[Column("CHANGED_WHO")]
public string
#if nullable
?
#endif
ChangedWho { get; set; }
[Column("CHANGED_WHEN")]
public DateTime
#if nullable
?
#endif
ChangedWhen { get; set; }
}
}

View File

@@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>dotnet-EnvelopeGenerator.Finalizer-6d5cc618-4159-4ff2-b600-8a15fbfa8099</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Quartz.AspNetCore" Version="3.15.1" />
<PackageReference Include="Quartzmon" Version="1.0.5" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.10" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageReference Include="Quartz" Version="3.15.1" />
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.15.1" />
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj" />
<ProjectReference Include="..\EnvelopeGenerator.DependencyInjection\EnvelopeGenerator.DependencyInjection.csproj" />
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Controllers\" />
<Folder Include="Controllers\" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.Database.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.Logging.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,35 @@
using EnvelopeGenerator.Finalizer.Job;
using Quartz;
namespace EnvelopeGenerator.Finalizer;
public static class Extensions
{
public static IServiceCollectionQuartzConfigurator ScheduleJobDefault<TJob>(this IServiceCollectionQuartzConfigurator q,
string croneEpression)
where TJob : IJob
{
var name = $"{typeof(TJob).FullName}";
var jobKey = new JobKey(name);
return q.ScheduleJob<TJob>(trigger => trigger
.WithIdentity(name + "-trigger")
.WithCronSchedule(croneEpression),
job => job.WithIdentity(jobKey)
);
}
public static IServiceCollectionQuartzConfigurator ScheduleJobDefault<TJob>(this IServiceCollectionQuartzConfigurator q,
IConfiguration configuration)
where TJob : IJob
{
var expression = configuration[$"{typeof(TJob).Name}:CronExpression"];
if (string.IsNullOrWhiteSpace(expression))
throw new InvalidOperationException(
"Cron expression for the Worker job is not configured. " +
"Please provide a valid cron schedule in the configuration under " +
$"'{typeof(TJob).FullName}:CronExpression'.");
return q.ScheduleJobDefault<TJob>(expression);
}
}

View File

@@ -0,0 +1,24 @@
using Quartz;
namespace EnvelopeGenerator.Finalizer.Job
{
public class EnvelopeTaskApiJob : IJob
{
private readonly ILogger<EnvelopeTaskApiJob> _logger;
public EnvelopeTaskApiJob(ILogger<EnvelopeTaskApiJob> logger)
{
_logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
if (_logger.IsEnabled(LogLevel.Information))
{
_logger.LogInformation("{jobName} running at: {time}", context.JobDetail.Key, DateTimeOffset.Now);
}
return Task.CompletedTask;
}
}
}

View File

@@ -0,0 +1,55 @@
using EnvelopeGenerator.Application.Envelopes.Queries;
using EnvelopeGenerator.Domain.Constants;
using EnvelopeGenerator.Finalizer.Models;
using MediatR;
using Microsoft.Extensions.Options;
using Quartz;
namespace EnvelopeGenerator.Finalizer.Job
{
public class FinishEnvelopeJob : IJob
{
private readonly ILogger<FinishEnvelopeJob> _logger;
private readonly GdPictureOptions _gdPictureOptions;
private readonly IMediator _mediator;
public FinishEnvelopeJob(ILogger<FinishEnvelopeJob> logger, IOptions<GdPictureOptions> gdPictureOptions, IMediator mediator)
{
_logger = logger;
_gdPictureOptions = gdPictureOptions.Value;
_mediator = mediator;
}
public async Task Execute(IJobExecutionContext context)
{
var cancel = context.CancellationToken;
var envelopes = await _mediator.Send(new ReadEnvelopeQuery()
{
Status = new() { Include = [EnvelopeStatus.EnvelopeCompletelySigned] },
HasDocResult = false
}, cancel);
foreach (var envelope in envelopes)
{
// add sub-steps
}
if (envelopes.Any())
_logger.LogInformation(
"Job '{JobName}' executed at {Timestamp}. {EnvelopeCount} envelope(s) successfully finalized. UUID(s): {EnvelopeUuids}",
context.JobDetail.Key.Name,
DateTimeOffset.Now,
envelopes.Count(),
string.Join(", ", envelopes.Select(e => e.Uuid))
);
else
_logger.LogInformation("Job '{JobName}' executed successfully at {Timestamp}. No envelopes were finalized.",
context.JobDetail.Key.Name,
DateTimeOffset.Now
);
}
}
}

View File

@@ -0,0 +1,134 @@
using CommandDotNet.Execution;
using EnvelopeGenerator.Application.ThirdPartyModules.Queries;
using EnvelopeGenerator.DependencyInjection;
using EnvelopeGenerator.Finalizer;
using EnvelopeGenerator.Finalizer.Job;
using EnvelopeGenerator.Finalizer.Models;
using EnvelopeGenerator.Infrastructure;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Quartz;
using Quartz.AspNetCore;
using Quartz.Impl;
using Quartzmon;
using Serilog;
// Load Serilog from appsettings.json
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(new ConfigurationBuilder()
.AddJsonFile("appsettings.Logging.json", optional: false, reloadOnChange: true)
.Build())
.CreateLogger();
try
{
Log.Information("Application is starting...");
var builder = WebApplication.CreateBuilder(args);
#region Logging
builder.Logging.ClearProviders();
builder.Logging.AddSerilog();
#endregion
#region Configuration
var config = builder.Configuration;
Directory
.GetFiles(builder.Environment.ContentRootPath, "appsettings.*.json", SearchOption.TopDirectoryOnly)
.Where(file => Path.GetFileName(file) != $"appsettings.Development.json")
.Where(file => Path.GetFileName(file) != $"appsettings.migration.json")
.ToList()
.ForEach(file => config.AddJsonFile(file, true, true));
#endregion
#region Web API Services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
#endregion
#region Quartz
builder.Services.AddQuartz(q =>
{
q.ScheduleJobDefault<FinishEnvelopeJob>(config);
q.ScheduleJobDefault<EnvelopeTaskApiJob>(config);
});
builder.Services.AddQuartzServer(options =>
{
options.WaitForJobsToComplete = true;
});
builder.Services.AddQuartzmon();
builder.Services.AddSingleton(provider =>
provider.GetRequiredService<ISchedulerFactory>().GetScheduler().Result
);
#endregion
#region Add DB Context, EG Inf. and Services
var cnnStrName = "Default";
var connStr = config.GetConnectionString(cnnStrName)
?? throw new InvalidOperationException($"Connection string '{cnnStrName}' is missing in the application configuration.");
builder.Services.AddEnvelopeGenerator(egOptions => egOptions
.AddLocalization()
.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = connStr;
options.SchemaName = "dbo";
options.TableName = "TBDD_CACHE";
})
.AddInfrastructure(opt =>
{
opt.AddDbTriggerParams(config);
opt.AddDbContext((provider, options) =>
{
var logger = provider.GetRequiredService<ILogger<EGDbContext>>();
var useInMemoryDb = config.GetValue<bool>("UseInMemoryDb");
var dbCtxOpt = useInMemoryDb ? options.UseInMemoryDatabase("EGInMemoryDb") : options.UseSqlServer(connStr);
dbCtxOpt.LogTo(log => logger.LogInformation("{log}", log), LogLevel.Trace)
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
});
})
.AddServices(config)
);
#endregion Add DB Context, EG Inf. and Services
var app = builder.Build();
#region Web API Middleware
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseQuartzmon(new QuartzmonOptions()
{
Scheduler = app.Services.GetRequiredService<IScheduler>(),
VirtualPathRoot = "/quartz"
});
app.MapControllers();
#endregion
app.Run();
Log.Information("The worker was stopped.");
}
catch (Exception ex)
{
Log.Fatal(ex, "Worker could not be started!");
}
finally
{
Log.CloseAndFlush();
}

View File

@@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:17119",
"sslPort": 44321
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5010",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "quartz",
"applicationUrl": "https://localhost:7141;http://localhost:5010",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": false,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,22 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"UseDbMigration": false,
"ConnectionStrings": {
"Default": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;",
"DbMigrationTest": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM_DATA_MIGR_TEST;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;"
},
"DbTriggerParams": {
"Envelope": [ "TBSIG_ENVELOPE_AFT_INS" ],
"History": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ],
"EmailOut": [ "TBEMLP_EMAIL_OUT_AFT_INS", "TBEMLP_EMAIL_OUT_AFT_UPD" ],
"EnvelopeReceiverReadOnly": [ "TBSIG_ENVELOPE_RECEIVER_READ_ONLY_UPD" ],
"Receiver": [],
"EmailTemplate": [ "TBSIG_EMAIL_TEMPLATE_AFT_UPD" ]
},
"UseInMemoryDb": false
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

View File

@@ -0,0 +1,8 @@
{
"FinishEnvelopeJob": {
"CronExpression": "* * * * * ?"
},
"EnvelopeTaskApiJob": {
"CronExpression": "* * * * * ?"
}
}

View File

@@ -0,0 +1,81 @@
{
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File" ],
"MinimumLevel": {
"Default": "Verbose",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "E:/LogFiles/Digital Data/signFlow.Finalizer/log.Verbose-.txt",
"rollingInterval": "Day",
"restrictedToMinimumLevel": "Verbose",
"retainedFileCountLimit": 30,
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "E:/LogFiles/Digital Data/signFlow.Finalizer/log.Debug-.txt",
"rollingInterval": "Day",
"restrictedToMinimumLevel": "Debug",
"retainedFileCountLimit": 30,
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "E:/LogFiles/Digital Data/signFlow.Finalizer/log.Info-.txt",
"rollingInterval": "Day",
"restrictedToMinimumLevel": "Information",
"retainedFileCountLimit": 30,
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "E:/LogFiles/Digital Data/signFlow.Finalizer/log.Warning-.txt",
"rollingInterval": "Day",
"restrictedToMinimumLevel": "Warning",
"retainedFileCountLimit": 30,
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "E:/LogFiles/Digital Data/signFlow.Finalizer/log.Error-.txt",
"rollingInterval": "Day",
"restrictedToMinimumLevel": "Error",
"retainedFileCountLimit": 30,
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "E:/LogFiles/Digital Data/signFlow.Finalizer/log.Fatal-.txt",
"rollingInterval": "Day",
"restrictedToMinimumLevel": "Fatal",
"retainedFileCountLimit": 30,
"outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ]
}
}

View File

@@ -0,0 +1,14 @@
{
"IgnoredLabels": {
"Label": [
"Date",
"Datum",
"ZIP",
"PLZ",
"Place",
"Ort",
"Position",
"Stellung"
]
}
}

View File

@@ -0,0 +1,3 @@
{
"GdPictureLicenseKey": "kG1Qf9PwmqgR8aDmIW2zI_ebj48RzqAJegRxcystEmkbTGQqfkNBdFOXIb6C_A00Ra8zZkrHdfjqzOPXK7kgkF2YDhvrqKfqh4WDug2vOt0qO31IommzkANSuLjZ4zmraoubyEVd25rE3veQ2h_j7tGIoH_LyIHmy24GaXsxdG0yCzIBMdiLbMMMDwcPY-809KeZ83Grv76OVhFvcbBWyYc251vou1N-kGg5_ZlHDgfWoY85gTLRxafjD3KS_i9ARW4BMiy36y8n7UP2jN8kGRnW_04ubpFtfjJqvtsrP_J9D0x7bqV8xtVtT5JI6dpKsVTiMgDCrIcoFSo5gCC1fw9oUopX4TDCkBQttO4-WHBlOeq9dG5Yb0otonVmJKaQA2tP6sMR-lZDs3ql_WI9t91yPWgpssrJUxSHDd27_LMTH_owJIqkF3NOJd9mYQuAv22oNKFYbH8e41pVKb8cT33Y9CgcQ_sy6YDA5PTuIRi67mjKge_nD9rd0IN213Ir9M_EFWqg9e4haWzIdHXQUo0md70kVhPX4UIH_BKJnxEEnFfoFRNMh77bB0N4jkcBEHPl-ghOERv8dOztf4vCnNpzzWvcLD2cqWIm6THy8XGGq9h4hp8aEreRleSMwv9QQAC7mjLwhQ1rBYkpUHlpTjhTLnMwHknl6HH0Z6zzmsgkRKVyfquv94Pd7QbQfZrRka0ss_48pf9p8hAywEn81Q=="
}

View File

@@ -105,6 +105,7 @@ try
});
builder.Services.AddOpenApi();
// TODO: Update to configure with EnvelopeGenerator.DependencyInjection
//AddEF Core dbcontext
var useDbMigration = Environment.GetEnvironmentVariable("MIGRATION_TEST_MODE") == true.ToString() || config.GetValue<bool>("UseDbMigration");
var cnnStrName = useDbMigration ? "DbMigrationTest" : "Default";
@@ -185,7 +186,7 @@ try
// Envelope generator serives
#pragma warning disable CS0618 // Type or member is obsolete
builder.Services
.AddEnvelopeGeneratorInfrastructureServices(opt =>
.AddEGInfrastructureServices(opt =>
{
opt.AddDbTriggerParams(config);
opt.AddDbContext((provider, options) =>

View File

@@ -38,10 +38,10 @@ namespace EnvelopeGenerator.Infrastructure
/// will be created per HTTP request (or per scope) within the dependency injection container.
/// </remarks>
[Obsolete("Use IRepository")]
public static IServiceCollection AddEnvelopeGeneratorInfrastructureServices(this IServiceCollection services, Action<Config> options)
public static IServiceCollection AddEGInfrastructureServices(this IServiceCollection services, Action<EGInfrastructureConfiguration> options)
{
// configure custom options
options(new Config(services));
options(new EGInfrastructureConfiguration(services));
#if NET
services.TryAddScoped<IConfigRepository, ConfigRepository>();
services.TryAddScoped<IDocumentReceiverElementRepository, DocumentReceiverElementRepository>();
@@ -160,11 +160,11 @@ namespace EnvelopeGenerator.Infrastructure
}
#endif
public class Config
public class EGInfrastructureConfiguration
{
private readonly IServiceCollection _services;
internal Config(IServiceCollection services)
internal EGInfrastructureConfiguration(IServiceCollection services)
{
_services = services;
}

View File

@@ -79,6 +79,8 @@ public abstract class EGDbContextBase : DbContext
public DbSet<ClientUser> ClientUsers { get; set; }
public DbSet<ThirdPartyModule> ThirdPartyModules { get; set; }
private readonly DbTriggerParams _triggers;
private readonly ILogger

View File

@@ -22,8 +22,8 @@
<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.4.0" />
<PackageReference Include="DigitalData.Core.Infrastructure" Version="2.4.5" />
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.5.0" />
<PackageReference Include="DigitalData.Core.Infrastructure" Version="2.5.0" />
<PackageReference Include="QuestPDF" Version="2025.7.1" />
</ItemGroup>

View File

@@ -525,7 +525,7 @@ namespace EnvelopeGenerator.Infrastructure.Migrations
});
});
modelBuilder.Entity("EnvelopeGenerator.Domain.Entities.Config", b =>
modelBuilder.Entity("EnvelopeGenerator.Domain.Entities.EGInfrastructureConfiguration", b =>
{
b.Property<string>("ExportPath")
.HasColumnType("nvarchar(256)")

View File

@@ -522,7 +522,7 @@ namespace EnvelopeGenerator.Infrastructure.Migrations
});
});
modelBuilder.Entity("EnvelopeGenerator.Domain.Entities.Config", b =>
modelBuilder.Entity("EnvelopeGenerator.Domain.Entities.EGInfrastructureConfiguration", b =>
{
b.Property<string>("ExportPath")
.HasColumnType("nvarchar(256)")

View File

@@ -20,7 +20,7 @@
<ItemGroup>
<PackageReference Include="Bogus" Version="35.6.3" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.4.0" />
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.5.0" />
<PackageReference Include="DigitalData.Core.Abstractions" Version="4.3.0" />
<PackageReference Include="DigitalData.Core.API" Version="2.2.1" />
<PackageReference Include="DigitalData.Core.Application" Version="3.4.0" />

View File

@@ -4,7 +4,6 @@ using EnvelopeGenerator.Application.Common.Extensions;
using EnvelopeGenerator.Application.Common.Interfaces.Services;
using EnvelopeGenerator.Application.Common.Notifications.DocSigned;
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
using EnvelopeGenerator.Application.Histories.Queries;
using EnvelopeGenerator.Domain.Constants;
using EnvelopeGenerator.Web.Extensions;
using MediatR;
@@ -12,6 +11,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Dynamic;
namespace EnvelopeGenerator.Web.Controllers;
@@ -58,9 +58,7 @@ public class AnnotationController : ControllerBase
// Again check if receiver has already signed
if (await _mediator.IsSignedAsync(uuid, signature, cancel))
return Problem(statusCode: 409);
else if (await _mediator.AnyHistoryAsync(uuid, new[] { EnvelopeStatus.EnvelopeRejected, EnvelopeStatus.DocumentRejected }, cancel))
return Problem(statusCode: 423);
return Problem(statusCode: 403);
var docSignedNotification = await _mediator
.ReadEnvelopeReceiverAsync(uuid, signature, cancel)
@@ -73,7 +71,7 @@ public class AnnotationController : ControllerBase
return Ok();
}
[Authorize(Roles = ReceiverRole.FullyAuth)]
[HttpPost("reject")]
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]

View File

@@ -95,23 +95,6 @@ public class EnvelopeController : ViewControllerBase
}
#endregion
#region UseAccessCode
if (!er.Envelope!.UseAccessCode)
{
(string? uuid, string? signature) = decoded.ParseEnvelopeReceiverId();
var er_secret_res = await _envRcvService.ReadWithSecretByUuidSignatureAsync(uuid: uuid!, signature: signature!);
if (er_secret_res.IsFailed)
{
_logger.LogNotice(er_secret_res.Notices);
return this.ViewEnvelopeNotFound();
}
var er_secret = er_secret_res.Data;
await HttpContext.SignInEnvelopeAsync(er_secret, ReceiverRole.FullyAuth);
return await CreateShowEnvelopeView(er_secret);
}
#endregion UseAccessCode
#region Send Access Code
bool accessCodeAlreadyRequested = await _historyService.AccessCodeAlreadyRequested(envelopeId: er.Envelope!.Id, userReference: er.Receiver!.EmailAddress);
if (!accessCodeAlreadyRequested)
@@ -138,7 +121,7 @@ public class EnvelopeController : ViewControllerBase
[HttpPost("{envelopeReceiverId}")]
[Obsolete("Use MediatR")]
public async Task<IActionResult> LogInEnvelope([FromRoute] string envelopeReceiverId, [FromForm] Auth auth, CancellationToken cancel)
public async Task<IActionResult> LogInEnvelope([FromRoute] string envelopeReceiverId, [FromForm] Auth auth)
{
try
{
@@ -162,15 +145,6 @@ public class EnvelopeController : ViewControllerBase
}
var er_secret = er_secret_res.Data;
//check rejection
var rejRcvrs = await _historyService.ReadRejectingReceivers(er_secret.Envelope!.Id);
if (rejRcvrs.Any())
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
ViewBag.IsExt = !rejRcvrs.Contains(er_secret.Receiver); //external if the current user is not rejected
return View("EnvelopeRejected", er_secret);
}
// show envelope if already logged in
if (User.IsInRole(ReceiverRole.FullyAuth))
return await CreateShowEnvelopeView(er_secret);
@@ -216,7 +190,7 @@ public class EnvelopeController : ViewControllerBase
return this.ViewInnerServiceError();
}
}
private async Task<IActionResult> CreateEnvelopeLockedView(EnvelopeReceiverDto er, CancellationToken cancel)
{
var uuidClaim = User.GetAuthEnvelopeUuid();

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PackageId>EnvelopeGenerator.Web</PackageId>
@@ -12,9 +12,9 @@
<PackageTags>digital data envelope generator web</PackageTags>
<Description>EnvelopeGenerator.Web is an ASP.NET MVC application developed to manage signing processes. It uses Entity Framework Core (EF Core) for database operations. The user interface for signing processes is developed with Razor View Engine (.cshtml files) and JavaScript under wwwroot, integrated with PSPDFKit. This integration allows users to view and sign documents seamlessly.</Description>
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
<Version>3.7.0</Version>
<AssemblyVersion>3.7.0</AssemblyVersion>
<FileVersion>3.7.0</FileVersion>
<Version>3.5.0</Version>
<AssemblyVersion>3.5.0</AssemblyVersion>
<FileVersion>3.5.0</FileVersion>
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
</PropertyGroup>
@@ -2094,13 +2094,20 @@
<None Include="wwwroot\lib\bootstrap-icons\icons\zoom-out.svg" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="BuildBundlerMinifier2022" Version="2.9.9" />
<PackageReference Include="DigitalData.Core.API" Version="2.2.1" />
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" />
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="3.1.1" />
<PackageReference Include="HtmlSanitizer" Version="8.0.865" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.20" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.15">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.20" />
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="7.0.20" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.2.5" />
@@ -2119,56 +2126,6 @@
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="BuildBundlerMinifier2022" Version="2.9.9" />
<PackageReference Include="DigitalData.Core.API" Version="2.2.1" />
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" />
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="3.1.1" />
<PackageReference Include="HtmlSanitizer" Version="8.0.865" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.4" />
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="7.0.20" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.2.5" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.0" />
<PackageReference Include="Quartz" Version="3.8.0" />
<PackageReference Include="Quartz.AspNetCore" Version="3.8.0" />
<PackageReference Include="Quartz.Plugins" Version="3.8.0" />
<PackageReference Include="Quartz.Serialization.Json" Version="3.8.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.0.1" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.1" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
<PackageReference Include="System.DirectoryServices" Version="8.0.0" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="8.0.1" />
<PackageReference Include="System.DirectoryServices.Protocols" Version="8.0.1" />
<PackageReference Include="System.Drawing.Common" Version="8.0.16" />
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="BuildBundlerMinifier2022" Version="2.9.9" />
<PackageReference Include="DigitalData.Core.API" Version="2.2.1" />
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" />
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="3.1.1" />
<PackageReference Include="HtmlSanitizer" Version="8.0.865" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.4" />
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="7.0.20" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.2.5" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.0" />
<PackageReference Include="Quartz" Version="3.8.0" />
<PackageReference Include="Quartz.AspNetCore" Version="3.8.0" />
<PackageReference Include="Quartz.Plugins" Version="3.8.0" />
<PackageReference Include="Quartz.Serialization.Json" Version="3.8.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.0.1" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="9.0.11" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
<PackageReference Include="System.DirectoryServices" Version="9.0.4" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="9.0.4" />
<PackageReference Include="System.DirectoryServices.Protocols" Version="9.0.4" />
<PackageReference Include="System.Drawing.Common" Version="9.0.5" />
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj" />
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />

View File

@@ -1,4 +1,3 @@
using EnvelopeGenerator.Application.Services;
using Microsoft.EntityFrameworkCore;
using NLog;
using Quartz;
@@ -16,7 +15,6 @@ using EnvelopeGenerator.Web.Sanitizers;
using EnvelopeGenerator.Web.Models.Annotation;
using DigitalData.UserManager.DependencyInjection;
using EnvelopeGenerator.Web.Middleware;
using EnvelopeGenerator.Application.Common.Interfaces.Services;
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Info("Logging initialized!");
@@ -88,6 +86,7 @@ try
builder.Services.AddSwaggerGen();
}
// TODO: Update to configure with EnvelopeGenerator.DependencyInjection
//AddEF Core dbcontext
var useDbMigration = Environment.GetEnvironmentVariable("MIGRATION_TEST_MODE") == true.ToString() || config.GetValue<bool>("UseDbMigration");
var cnnStrName = useDbMigration ? "DbMigrationTest" : "Default";
@@ -103,7 +102,7 @@ try
// Add envelope generator services
#pragma warning disable CS0618 // Type or member is obsolete
builder.Services.AddEnvelopeGeneratorInfrastructureServices(
builder.Services.AddEGInfrastructureServices(
opt =>
{
opt.AddDbTriggerParams(config);
@@ -186,15 +185,8 @@ try
builder.Services.Configure<Cultures>(config.GetSection("Cultures"));
builder.Services.AddSingleton(sp => sp.GetRequiredService<IOptions<Cultures>>().Value);
// Register mail services
#pragma warning disable CS0618 // Type or member is obsolete
builder.Services.AddScoped<IEnvelopeMailService, EnvelopeMailService>();
#pragma warning restore CS0618 // Type or member is obsolete
builder.Services.AddDispatcher<EGDbContext>();
builder.Services.AddMemoryCache();
builder.ConfigureBySection<CustomImages>();
builder.ConfigureBySection<AnnotationParams>();

View File

@@ -11,10 +11,12 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
<ExcludeApp_Data>false</ExcludeApp_Data>
<ProjectGuid>5e0e17c0-ff5a-4246-bf87-1add85376a27</ProjectGuid>
<DesktopBuildPackageLocation>P:\Install .Net\0 DD - Smart UP\signFLOW\Web\net8\$(Version)\EnvelopeGenerator.Web.zip</DesktopBuildPackageLocation>
<DesktopBuildPackageLocation>P:\Install .Net\0 DD - Smart UP\signFLOW\Web\net9\win64\$(Version)\EnvelopeGenerator.Web.zip</DesktopBuildPackageLocation>
<PackageAsSingleFile>true</PackageAsSingleFile>
<DeployIisAppPath>EnvelopeGenerator</DeployIisAppPath>
<_TargetId>IISWebDeployPackage</_TargetId>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>

View File

@@ -79,7 +79,7 @@
}
else
{
<div class="markdown">@(envelope?.Message)</div>
<h6>@($"{@envelope?.Message}")</h6>
}
<p>
<small class="text-body-secondary">

View File

@@ -49,7 +49,6 @@
<script src="~/lib/bootstrap/dist/js/bootstrap.min.js"></script>
<script src="~/lib/sweetalert2/sweetalert2.min.js"></script>
<script src="~/lib/alertifyjs/alertify.min.js"></script>
<script src="~/lib/marked/marked.umd.min.js"></script>
<script src="~/js/lazy.min.js" asp-append-version="true"></script>
<script src="~/js/ui.min.js" asp-append-version="true"></script>
<script src="~/js/annotation.js" asp-append-version="true"></script>
@@ -96,6 +95,5 @@
</div>
<a href="/privacy-policy.@(_localizer.Culture()).html" target="_blank">@_localizer.Privacy()</a>
</footer>
<script src="~/js/markdown.min.js" asp-append-version="true"></script>
</body>
</html>

View File

@@ -41,12 +41,6 @@
"wwwroot/js/util.js"
]
},
{
"outputFileName": "wwwroot/js/markdown.min.js",
"inputFiles": [
"wwwroot/js/markdown.js"
]
},
{
"outputFileName": "wwwroot/css/error-space.min.css",
"inputFiles": [

View File

@@ -255,7 +255,7 @@ class App {
});
if (!res.ok) {
if (res.status === 409) {
if (res.status === 403) {
Swal.fire({
title: 'Warnung',
text: 'Umschlag ist nicht mehr verfügbar.',
@@ -263,17 +263,6 @@ class App {
})
return false
}
else if (res.status === 423) {
Swal.fire({
title: 'Info',
text: 'Dokument wurde von einem Empfänger abgelehnt. Sie werden weitergeleitet...',
icon: 'info',
timer: 2000,
showConfirmButton: false
}).then(() => {
location.reload();
});
}
else {
throw new Error()
}

View File

@@ -1,3 +1,3 @@
class App{constructor(n,t,i,r,u,f){this.container=f??`#${this.constructor.name.toLowerCase()}`;this.envelopeKey=n;this.pdfKit=null;this.currentDocument=t.envelope.documents[0];this.currentReceiver=t.receiver;this.signatureCount=t.envelope.documents[0].elements.length;this.envelopeReceiver=t;this.documentBytes=i;this.licenseKey=r;this.locale=u}async init(){this.pdfKit=await loadPSPDFKit(this.documentBytes,this.container,this.licenseKey,this.locale);addToolbarItems(this.pdfKit,this.handleClick.bind(this));this.pdfKit.addEventListener("annotations.load",this.handleAnnotationsLoad.bind(this));this.pdfKit.addEventListener("annotations.change",this.handleAnnotationsChange.bind(this));this.pdfKit.addEventListener("annotations.create",this.handleAnnotationsCreate.bind(this));this.pdfKit.addEventListener("annotations.willChange",()=>{Comp.ActPanel.Toggle()});try{let n=await createAnnotations(this.currentDocument,this.envelopeReceiver.envelopeId,this.envelopeReceiver.receiverId);await this.pdfKit.create(n)}catch(n){console.error("Error loading annotations:",n)}[...document.getElementsByClassName("btn_refresh")].forEach(n=>n.addEventListener("click",()=>this.handleClick("RESET")));[...document.getElementsByClassName("btn_complete")].forEach(n=>n.addEventListener("click",()=>this.handleClick("FINISH")));[...document.getElementsByClassName("btn_reject")].forEach(n=>n.addEventListener("click",()=>this.handleClick("REJECT")))}handleAnnotationsLoad(n){n.toJS()}handleAnnotationsChange(){}async handleAnnotationsCreate(n){const t=n.toJS()[0],i=!!t.formFieldName,r=!!t.isSignature;if(i===!1&&r===!0){const r=t.boundingBox.left-20,u=t.boundingBox.top-20,n=150,i=75,f=new Date,e=await createAnnotationFrameBlob(this.envelopeReceiver.name,this.currentReceiver.signature,f,n,i),o=await fetch(e),s=await o.blob(),h=await this.pdfKit.createAttachment(s),c=createImageAnnotation(new PSPDFKit.Geometry.Rect({left:r,top:u,width:n,height:i}),t.pageIndex,h,generateId(this.envelopeReceiver.envelopeId,this.envelopeReceiver.receiverId,this.fakeElementId--,"signed"));this.pdfKit.create(c)}}async handleClick(n){let t=!1;switch(n){case"RESET":t=await this.handleReset(null);Comp.SignatureProgress.SignedCount=0;t.isConfirmed&&Swal.fire({title:"Erfolg",text:"Dokument wurde zurückgesetzt",icon:"info"});break;case"FINISH":t=await this.handleFinish(null);t==!0&&(window.location.href=`/Envelope/${this.envelopeKey}`);break;case"REJECT":Swal.fire({title:localized.rejection,html:`<div class="text-start fs-6 p-0 m-0">${localized.rejectionReasonQ}</div>`,icon:"question",input:"text",inputAttributes:{autocapitalize:"off"},showCancelButton:!0,confirmButtonColor:"#3085d6",cancelButtonColor:"#d33",confirmButtonText:localized.complete,cancelButtonText:localized.back,showLoaderOnConfirm:!0,preConfirm:async n=>{try{return await rejectEnvelope(n)}catch(t){Swal.showValidationMessage(`
Request failed: ${t}
`)}},allowOutsideClick:()=>!Swal.isLoading()}).then(n=>{if(n.isConfirmed){const t=n.value;t.ok?reload():Swal.showValidationMessage(`Request failed: ${t.message}`)}});break;case"COPY_URL":const n=window.location.href.replace(/\/readonly/gi,"");navigator.clipboard.writeText(n).then(function(){bsNotify("Kopiert",{alert_type:"success",delay:4,icon_name:"check_circle"})}).catch(function(){bsNotify("Unerwarteter Fehler",{alert_type:"danger",delay:4,icon_name:"error"})});break;case"SHARE":Comp.ShareBackdrop.show();break;case"LOGOUT":await logout()}}async handleFinish(){const n=await this.pdfKit.exportInstantJSON(),t=n.formFieldValues,r=t.filter(n=>isFieldRequired(n)),u=r.some(n=>n.value===undefined||n.value===null||n.value==="");if(u)return Swal.fire({title:"Warnung",text:"Bitte füllen Sie alle Standortinformationen vollständig aus!",icon:"warning"}),!1;const f=new RegExp("^[a-zA-Z\\u0080-\\u024F]+(?:([\\ \\-\\']|(\\.\\ ))[a-zA-Z\\u0080-\\u024F]+)*$"),e=t.filter(n=>isCityField(n));for(var i of e)if(!IS_MOBILE_DEVICE&&!f.test(i.value))return Swal.fire({title:"Warnung",text:`Bitte überprüfen Sie die eingegebene Ortsangabe "${i.value}" auf korrekte Formatierung. Beispiele für richtige Formate sind: München, Île-de-France, Sauðárkrókur, San Francisco, St. Catharines usw.`,icon:"warning"}),!1;const o=await this.validateAnnotations(this.signatureCount);return o===!1?(Swal.fire({title:"Warnung",text:"Es wurden nicht alle Signaturfelder ausgefüllt!",icon:"warning"}),!1):Swal.fire({title:localized.confirmation,html:`<div class="text-start fs-6 p-0 m-0">${localized.sigAgree}</div>`,icon:"question",showCancelButton:!0,confirmButtonColor:"#3085d6",cancelButtonColor:"#d33",confirmButtonText:localized.finalize,cancelButtonText:localized.back}).then(async t=>{if(t.isConfirmed){try{await this.pdfKit.save()}catch(i){return Swal.fire({title:"Fehler",text:"Umschlag konnte nicht signiert werden!",icon:"error"}),!1}try{const t=await signEnvelope({instant:n,structured:mapSignature(n)});if(t.ok)return!0;if(t.status===409)return Swal.fire({title:"Warnung",text:"Umschlag ist nicht mehr verfügbar.",icon:"warning"}),!1;if(t.status===423)Swal.fire({title:"Info",text:"Dokument wurde von einem Empfänger abgelehnt. Sie werden weitergeleitet...",icon:"info",timer:2e3,showConfirmButton:!1}).then(()=>{location.reload()});else throw new Error;}catch(i){return Swal.fire({title:"Fehler",text:"Umschlag konnte nicht signiert werden!",icon:"error"}),!1}}else return!1})}async validateAnnotations(n){const t=await getAnnotations(this.pdfKit),i=t.map(n=>n.toJS()).filter(n=>n.isSignature);return n<=i.length}async handleReset(){const n=Swal.fire({title:"Sind sie sicher?",text:"Wollen Sie das Dokument und alle erstellten Signaturen zurücksetzen?",icon:"question",showCancelButton:!0});if(n.isConfirmed){const n=await deleteAnnotations(this.pdfKit)}return n}fakeElementId=0;}
`)}},allowOutsideClick:()=>!Swal.isLoading()}).then(n=>{if(n.isConfirmed){const t=n.value;t.ok?reload():Swal.showValidationMessage(`Request failed: ${t.message}`)}});break;case"COPY_URL":const n=window.location.href.replace(/\/readonly/gi,"");navigator.clipboard.writeText(n).then(function(){bsNotify("Kopiert",{alert_type:"success",delay:4,icon_name:"check_circle"})}).catch(function(){bsNotify("Unerwarteter Fehler",{alert_type:"danger",delay:4,icon_name:"error"})});break;case"SHARE":Comp.ShareBackdrop.show();break;case"LOGOUT":await logout()}}async handleFinish(){const n=await this.pdfKit.exportInstantJSON(),t=n.formFieldValues,r=t.filter(n=>isFieldRequired(n)),u=r.some(n=>n.value===undefined||n.value===null||n.value==="");if(u)return Swal.fire({title:"Warnung",text:"Bitte füllen Sie alle Standortinformationen vollständig aus!",icon:"warning"}),!1;const f=new RegExp("^[a-zA-Z\\u0080-\\u024F]+(?:([\\ \\-\\']|(\\.\\ ))[a-zA-Z\\u0080-\\u024F]+)*$"),e=t.filter(n=>isCityField(n));for(var i of e)if(!IS_MOBILE_DEVICE&&!f.test(i.value))return Swal.fire({title:"Warnung",text:`Bitte überprüfen Sie die eingegebene Ortsangabe "${i.value}" auf korrekte Formatierung. Beispiele für richtige Formate sind: München, Île-de-France, Sauðárkrókur, San Francisco, St. Catharines usw.`,icon:"warning"}),!1;const o=await this.validateAnnotations(this.signatureCount);return o===!1?(Swal.fire({title:"Warnung",text:"Es wurden nicht alle Signaturfelder ausgefüllt!",icon:"warning"}),!1):Swal.fire({title:localized.confirmation,html:`<div class="text-start fs-6 p-0 m-0">${localized.sigAgree}</div>`,icon:"question",showCancelButton:!0,confirmButtonColor:"#3085d6",cancelButtonColor:"#d33",confirmButtonText:localized.finalize,cancelButtonText:localized.back}).then(async t=>{if(t.isConfirmed){try{await this.pdfKit.save()}catch(i){return Swal.fire({title:"Fehler",text:"Umschlag konnte nicht signiert werden!",icon:"error"}),!1}try{const t=await signEnvelope({instant:n,structured:mapSignature(n)});if(t.ok)return!0;if(t.status===403)return Swal.fire({title:"Warnung",text:"Umschlag ist nicht mehr verfügbar.",icon:"warning"}),!1;throw new Error;}catch(i){return Swal.fire({title:"Fehler",text:"Umschlag konnte nicht signiert werden!",icon:"error"}),!1}}else return!1})}async validateAnnotations(n){const t=await getAnnotations(this.pdfKit),i=t.map(n=>n.toJS()).filter(n=>n.isSignature);return n<=i.length}async handleReset(){const n=Swal.fire({title:"Sind sie sicher?",text:"Wollen Sie das Dokument und alle erstellten Signaturen zurücksetzen?",icon:"question",showCancelButton:!0});if(n.isConfirmed){const n=await deleteAnnotations(this.pdfKit)}return n}fakeElementId=0;}

View File

@@ -1,11 +0,0 @@
marked.use({
async: true,
breaks: true,
gfm: true,
});
(async () => {
for (const el of document.querySelectorAll('.markdown')) {
el.innerHTML = await marked.parse(el.textContent.replace(/(\t| )/g, "&emsp;"));
}
})();

View File

@@ -1 +0,0 @@
marked.use({"async":!0,breaks:!0,gfm:!0});(async()=>{for(const n of document.querySelectorAll(".markdown"))n.innerHTML=await marked.parse(n.textContent.replace(/(\t| )/g,"&emsp;"))})();

File diff suppressed because one or more lines are too long

View File

@@ -37,6 +37,10 @@ Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "EnvelopeGenerator.Form", "E
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.PdfEditor", "EnvelopeGenerator.PdfEditor\EnvelopeGenerator.PdfEditor.csproj", "{211619F5-AE25-4BA5-A552-BACAFE0632D3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.DependencyInjection", "EnvelopeGenerator.DependencyInjection\EnvelopeGenerator.DependencyInjection.csproj", "{B97DE7DD-3190-4A84-85E9-E57AD735BE61}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.Finalizer", "EnvelopeGenerator.Finalizer\EnvelopeGenerator.Finalizer.csproj", "{C4970E6C-DB2E-48C5-B3C5-2AF589405ED9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -91,6 +95,14 @@ Global
{211619F5-AE25-4BA5-A552-BACAFE0632D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{211619F5-AE25-4BA5-A552-BACAFE0632D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{211619F5-AE25-4BA5-A552-BACAFE0632D3}.Release|Any CPU.Build.0 = Release|Any CPU
{B97DE7DD-3190-4A84-85E9-E57AD735BE61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B97DE7DD-3190-4A84-85E9-E57AD735BE61}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B97DE7DD-3190-4A84-85E9-E57AD735BE61}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B97DE7DD-3190-4A84-85E9-E57AD735BE61}.Release|Any CPU.Build.0 = Release|Any CPU
{C4970E6C-DB2E-48C5-B3C5-2AF589405ED9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4970E6C-DB2E-48C5-B3C5-2AF589405ED9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4970E6C-DB2E-48C5-B3C5-2AF589405ED9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4970E6C-DB2E-48C5-B3C5-2AF589405ED9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -111,6 +123,8 @@ Global
{A9F9B431-BB9B-49B8-9E2C-0703634A653A} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
{6D56C01F-D6CB-4D8A-BD3D-4FD34326998C} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
{211619F5-AE25-4BA5-A552-BACAFE0632D3} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
{B97DE7DD-3190-4A84-85E9-E57AD735BE61} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
{C4970E6C-DB2E-48C5-B3C5-2AF589405ED9} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {73E60370-756D-45AD-A19A-C40A02DACCC7}