Compare commits
75 Commits
master
...
feat/servi
| Author | SHA1 | Date | |
|---|---|---|---|
| 7af934ea19 | |||
| b65367fb6d | |||
| 25c31108cb | |||
| 4083833b19 | |||
| f53bc65acf | |||
| 070d9be00c | |||
| 6f7b04a26e | |||
| 473358e2b9 | |||
| 8f3aa69cbf | |||
| eededeb1f1 | |||
| 737774f077 | |||
| 69499273cc | |||
| ead33ab2e7 | |||
| d1e2840617 | |||
| a39ef6a0e2 | |||
| 7d620988d8 | |||
| cc4a7d8c20 | |||
| 0b8068f926 | |||
| 9fd7a68798 | |||
| d6e2690bb8 | |||
| f04385a03c | |||
| c88e7b2b9e | |||
| 0ee7ec82d6 | |||
| a6d6dc8c4d | |||
| ab038df8b9 | |||
| 302249451b | |||
| a4082fca45 | |||
| f3ae8a9c49 | |||
| d6058c41d0 | |||
| 79d093c492 | |||
| 56c65b6fbb | |||
| 2af18842c4 | |||
| 41e0d4691b | |||
| 64e0a4f749 | |||
| 4cf54d36b9 | |||
| 020cecabf3 | |||
| 40c899e47e | |||
| bae62c7c08 | |||
| 41dde6f016 | |||
| c5b167f0d4 | |||
| a70faebde6 | |||
| 3e01052579 | |||
| f49b907574 | |||
| 86ed96ae76 | |||
| 9dbfdaa15e | |||
| 9d66f1d19e | |||
| 14cef05d02 | |||
| bdfb973d55 | |||
| 15a18b1bfd | |||
| 6fac1cd96a | |||
| 79d2636c14 | |||
| 2172ce8203 | |||
| 51ab9fb094 | |||
| d8a002cd22 | |||
| e36684820e | |||
| 2a5d953623 | |||
| 0aba9e91e2 | |||
| 1a0973075b | |||
| b8fd26611c | |||
| 0ca372bf45 | |||
| 5230076d5d | |||
| b28084bf19 | |||
| cbc983e070 | |||
| 3b06f3fdac | |||
| a12d74871d | |||
| 45b715ed74 | |||
| 9d5e2e6ad2 | |||
| c5d2d79563 | |||
| 15d4573321 | |||
| eb46590c1d | |||
| c93c32307a | |||
| 41cca7fa64 | |||
| b01c17ab18 | |||
| c8834dc3be | |||
| e385fdda95 |
13
EnvelopeGenerator.Application/Common/CacheKey.cs
Normal file
13
EnvelopeGenerator.Application/Common/CacheKey.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace EnvelopeGenerator.Application.Common;
|
||||
|
||||
// TODO: merge other cache keys here as well, e.g. for templates, etc.
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class CacheKey
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static readonly Guid DefaultConfig = Guid.NewGuid();
|
||||
}
|
||||
@@ -8,6 +8,11 @@ namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
public class ConfigDto
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the default document path.
|
||||
/// </summary>
|
||||
public string? DocumentPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sending profile identifier.
|
||||
/// </summary>
|
||||
@@ -26,5 +31,30 @@ public class ConfigDto
|
||||
/// <summary>
|
||||
/// Gets or sets the path where exports will be saved.
|
||||
/// </summary>
|
||||
public string? ExportPath { get; set; }
|
||||
public string ExportPath { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the creation timestamp.
|
||||
/// </summary>
|
||||
public DateTime AddedWhen { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the last update timestamp.
|
||||
/// </summary>
|
||||
public DateTime? ChangedWhen { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the legacy tinyint GUID field.
|
||||
/// </summary>
|
||||
public byte Guid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether default TFA is enabled.
|
||||
/// </summary>
|
||||
public bool DefTfaEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether default TFA uses phone.
|
||||
/// </summary>
|
||||
public bool DefTfaWithPhone { get; set; }
|
||||
}
|
||||
@@ -44,6 +44,16 @@ public record EnvelopeDto : IEnvelope
|
||||
[TemplatePlaceholder("[MESSAGE]")]
|
||||
public string Message { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public DateTime? ExpiresWhen { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public DateTime? ExpiresWarningWhen { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -60,6 +70,11 @@ public record EnvelopeDto : IEnvelope
|
||||
[TemplatePlaceholder("[DOCUMENT_TITLE]")]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Default value is string.Empty
|
||||
/// </summary>
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -70,6 +85,21 @@ public record EnvelopeDto : IEnvelope
|
||||
/// </summary>
|
||||
public string Language { get; set; } = "de-DE";
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool SendReminderEmails { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int? FirstReminderDays { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int? ReminderIntervalDays { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -90,6 +120,26 @@ public record EnvelopeDto : IEnvelope
|
||||
/// </summary>
|
||||
public bool UseAccessCode { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int? FinalEmailToCreator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int? FinalEmailToReceivers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int? ExpiresWhenDays { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int? ExpiresWarningWhenDays { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
using AutoMapper;
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using DigitalData.Core.Exceptions;
|
||||
using EnvelopeGenerator.Application.Common;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Configuration.Queries;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public record ReadDefaultConfigQuery : IRequest<ConfigDto>
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool EnforceSingleResult { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class ReadDefaultConfigQueryHandler : IRequestHandler<ReadDefaultConfigQuery, ConfigDto>
|
||||
{
|
||||
private readonly IRepository<Config> _repo;
|
||||
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="mapper"></param>
|
||||
/// <param name="cache"></param>
|
||||
public ReadDefaultConfigQueryHandler(IRepository<Config> repo, IMapper mapper, IMemoryCache cache)
|
||||
{
|
||||
_repo = repo;
|
||||
_mapper = mapper;
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public async Task<ConfigDto> Handle(ReadDefaultConfigQuery request, CancellationToken cancel)
|
||||
{
|
||||
var config = await _cache.GetOrCreateAsync(CacheKey.DefaultConfig, entry =>
|
||||
{
|
||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
|
||||
return request.EnforceSingleResult
|
||||
? _repo.Query.SingleOrDefaultAsync(cancel)
|
||||
: _repo.Query.FirstOrDefaultAsync(cancel)
|
||||
?? throw new NotFoundException("Default configuration could not be found. Ensure at least one configuration record exists in the database.");
|
||||
});
|
||||
|
||||
return _mapper.Map<ConfigDto>(config);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Application.Common.Interfaces.Repositories;
|
||||
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
using EnvelopeGenerator.Application.Common;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Services;
|
||||
|
||||
@@ -16,8 +17,6 @@ namespace EnvelopeGenerator.Application.Services;
|
||||
[Obsolete("Use MediatR")]
|
||||
public class ConfigService : ReadService<IConfigRepository, ConfigDto, Config, int>, IConfigService
|
||||
{
|
||||
private static readonly Guid DefaultConfigCacheId = Guid.NewGuid();
|
||||
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
private readonly ILogger<ConfigService> _logger;
|
||||
@@ -62,7 +61,7 @@ public class ConfigService : ReadService<IConfigRepository, ConfigDto, Config, i
|
||||
/// </exception>
|
||||
public async Task<ConfigDto> ReadDefaultAsync()
|
||||
{
|
||||
var config = await _cache.GetOrCreateAsync(DefaultConfigCacheId, _ => ReadFirstAsync().ThenAsync(
|
||||
var config = await _cache.GetOrCreateAsync(CacheKey.DefaultConfig, _ => ReadFirstAsync().ThenAsync(
|
||||
Success: config => config,
|
||||
Fail: (mssg, ntc) =>
|
||||
{
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
#if NETFRAMEWORK
|
||||
using System;
|
||||
#endif
|
||||
|
||||
namespace EnvelopeGenerator.Domain.Entities
|
||||
{
|
||||
[Table("TBSIG_CONFIG", Schema = "dbo")]
|
||||
public class Config
|
||||
{
|
||||
[Column("DOCUMENT_PATH", TypeName = "nvarchar(256)")]
|
||||
public string DocumentPath { get; set; }
|
||||
|
||||
[Column("SENDING_PROFILE", TypeName = "int")]
|
||||
[Required]
|
||||
public int SendingProfile { get; set; }
|
||||
@@ -19,5 +25,24 @@ namespace EnvelopeGenerator.Domain.Entities
|
||||
|
||||
[Column("EXPORT_PATH", TypeName = "nvarchar(256)")]
|
||||
public string ExportPath { get; set; }
|
||||
|
||||
[Column("ADDED_WHEN", TypeName = "datetime")]
|
||||
[Required]
|
||||
public DateTime AddedWhen { get; set; }
|
||||
|
||||
[Column("CHANGED_WHEN", TypeName = "datetime")]
|
||||
public DateTime? ChangedWhen { get; set; }
|
||||
|
||||
[Column("GUID", TypeName = "tinyint")]
|
||||
[Required]
|
||||
public byte Guid { get; set; }
|
||||
|
||||
[Column("DEF_TFA_ENABLED", TypeName = "bit")]
|
||||
[Required]
|
||||
public bool DefTfaEnabled { get; set; }
|
||||
|
||||
[Column("DEF_TFA_WITH_PHONE", TypeName = "bit")]
|
||||
[Required]
|
||||
public bool DefTfaWithPhone { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -76,11 +76,14 @@ namespace EnvelopeGenerator.Domain.Entities
|
||||
#if nullable
|
||||
?
|
||||
#endif
|
||||
Title
|
||||
{ get; set; }
|
||||
Title { get; set; }
|
||||
|
||||
[Column("COMMENT", TypeName = "nvarchar(128)")]
|
||||
public string Comment { get; set; }
|
||||
public string
|
||||
#if nullable
|
||||
?
|
||||
#endif
|
||||
Comment { get; set; }
|
||||
|
||||
[Column("CONTRACT_TYPE")]
|
||||
public int? ContractType { get; set; }
|
||||
@@ -151,8 +154,7 @@ namespace EnvelopeGenerator.Domain.Entities
|
||||
#if nullable
|
||||
?
|
||||
#endif
|
||||
Type
|
||||
{ get; set; }
|
||||
Type { get; set; }
|
||||
|
||||
#if NETFRAMEWORK
|
||||
[NotMapped]
|
||||
@@ -166,22 +168,26 @@ namespace EnvelopeGenerator.Domain.Entities
|
||||
#if nullable
|
||||
?
|
||||
#endif
|
||||
Documents
|
||||
{ get; set; }
|
||||
Documents { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public Document
|
||||
#if nullable
|
||||
?
|
||||
#endif
|
||||
DefaultDocument => Documents?.FirstOrDefault();
|
||||
|
||||
public List<History>
|
||||
#if nullable
|
||||
?
|
||||
#endif
|
||||
Histories
|
||||
{ get; set; }
|
||||
Histories { get; set; }
|
||||
|
||||
public List<EnvelopeReceiver>
|
||||
#if nullable
|
||||
?
|
||||
#endif
|
||||
EnvelopeReceivers
|
||||
{ get; set; }
|
||||
EnvelopeReceivers { get; set; }
|
||||
|
||||
//#if NETFRAMEWORK
|
||||
/// <summary>
|
||||
|
||||
36
EnvelopeGenerator.Domain/Entities/EnvelopeReport.cs
Normal file
36
EnvelopeGenerator.Domain/Entities/EnvelopeReport.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace EnvelopeGenerator.Domain.Entities
|
||||
{
|
||||
[Table("VWSIG_ENVELOPE_REPORT", Schema = "dbo")]
|
||||
public class EnvelopeReport
|
||||
{
|
||||
[Column("ENVELOPE_ID", TypeName = "int")]
|
||||
[Required]
|
||||
public int EnvelopeId { get; set; }
|
||||
|
||||
[Column("HEAD_UUID", TypeName = "nvarchar(36)")]
|
||||
[Required]
|
||||
public string HeadUuid { get; set; }
|
||||
|
||||
[Column("HEAD_TITLE", TypeName = "nvarchar(128)")]
|
||||
public string HeadTitle { get; set; }
|
||||
|
||||
[Column("HEAD_MESSAGE", TypeName = "nvarchar(max)")]
|
||||
[Required]
|
||||
public string HeadMessage { get; set; }
|
||||
|
||||
[Column("POS_STATUS", TypeName = "int")]
|
||||
[Required]
|
||||
public int PosStatus { get; set; }
|
||||
|
||||
[Column("POS_WHEN", TypeName = "datetime")]
|
||||
public DateTime? PosWhen { get; set; }
|
||||
|
||||
[Column("POS_WHO", TypeName = "nvarchar(128)")]
|
||||
[Required]
|
||||
public string PosWho { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,9 @@ using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using EnvelopeGenerator.Domain.Interfaces.Auditing;
|
||||
#if NET
|
||||
using System.Text.Json.Serialization;
|
||||
#endif
|
||||
#if NETFRAMEWORK
|
||||
using System.Collections.Generic;
|
||||
#endif
|
||||
@@ -109,22 +112,25 @@ namespace EnvelopeGenerator.Domain.Entities
|
||||
#if nullable
|
||||
?
|
||||
#endif
|
||||
Receiver
|
||||
{ get; set; }
|
||||
Receiver { get; set; }
|
||||
|
||||
public virtual IEnumerable<ElementAnnotation>
|
||||
#if nullable
|
||||
?
|
||||
#endif
|
||||
Annotations
|
||||
{ get; set; }
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,8 @@ public abstract class EGDbContextBase : DbContext
|
||||
|
||||
public DbSet<ClientUser> ClientUsers { get; set; }
|
||||
|
||||
public DbSet<EnvelopeReport> EnvelopeReports { get; set; }
|
||||
|
||||
private readonly DbTriggerParams _triggers;
|
||||
|
||||
private readonly ILogger
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||
<PackageReference Include="GdPicture" Version="14.3.3" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.17" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.17" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Quartz" Version="3.8.0" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="8.0.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.16" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
|
||||
<PackageReference Include="DevExpress.Reporting.Core" Version="24.2.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\EnvelopeGenerator.PdfEditor\EnvelopeGenerator.PdfEditor.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Controllers\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace EnvelopeGenerator.ServiceHost.Exceptions;
|
||||
|
||||
public class BurnAnnotationException : ApplicationException
|
||||
{
|
||||
public BurnAnnotationException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public BurnAnnotationException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace EnvelopeGenerator.ServiceHost.Exceptions;
|
||||
|
||||
public class CreateReportException : ApplicationException
|
||||
{
|
||||
public CreateReportException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public CreateReportException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace EnvelopeGenerator.ServiceHost.Exceptions;
|
||||
|
||||
public class ExportDocumentException : ApplicationException
|
||||
{
|
||||
public ExportDocumentException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public ExportDocumentException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace EnvelopeGenerator.ServiceHost.Exceptions;
|
||||
|
||||
public class MergeDocumentException : ApplicationException
|
||||
{
|
||||
public MergeDocumentException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public MergeDocumentException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Data;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Extensions;
|
||||
|
||||
public static class DataRowExtensions
|
||||
{
|
||||
public static T ItemEx<T>(this DataRow row, string columnName, T defaultValue)
|
||||
{
|
||||
if (!row.Table.Columns.Contains(columnName))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
var value = row[columnName];
|
||||
if (value is DBNull or null)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
|
||||
public static string ItemEx(this DataRow row, string columnName, string defaultValue)
|
||||
{
|
||||
return row.ItemEx<string>(columnName, defaultValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using DigitalData.Modules.Database;
|
||||
using EnvelopeGenerator.ServiceHost.Jobs;
|
||||
using EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
using GdPicture14;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Extensions;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
[Obsolete("Check obsoleted services")]
|
||||
public static IServiceCollection AddFinalizeDocumentJob(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.Configure<WorkerOptions>(configuration.GetSection(nameof(WorkerOptions)));
|
||||
services.AddSingleton<FinalizeDocumentJob>();
|
||||
services.AddScoped<ActionService>();
|
||||
services.AddSingleton<TempFiles>();
|
||||
services.AddScoped<PDFBurner>();
|
||||
services.AddScoped<PDFMerger>();
|
||||
services.AddScoped<ReportModel>();
|
||||
services.AddScoped<MSSQLServer>();
|
||||
|
||||
//TODO: Check lifetime of services. They might be singleton or scoped.
|
||||
services.AddTransient<GdViewer>();
|
||||
// Add LicenseManager
|
||||
services.AddTransient(provider =>
|
||||
{
|
||||
var options = provider.GetRequiredService<IOptions<WorkerOptions>>().Value;
|
||||
var licenseManager = new LicenseManager();
|
||||
licenseManager.RegisterKEY(options.GdPictureLicenseKey);
|
||||
return licenseManager;
|
||||
});
|
||||
services.AddTransient<AnnotationManager>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
11
EnvelopeGenerator.ServiceHost/Extensions/LoggerExtensions.cs
Normal file
11
EnvelopeGenerator.ServiceHost/Extensions/LoggerExtensions.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using EnvelopeGenerator.ServiceHost.Extensions;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Extensions;
|
||||
|
||||
public static class LoggerExtensions
|
||||
{
|
||||
public static void LogError(this ILogger logger, Exception exception)
|
||||
{
|
||||
logger.LogError(exception, "{message}", exception.Message);
|
||||
}
|
||||
}
|
||||
35
EnvelopeGenerator.ServiceHost/Jobs/ActionService.cs
Normal file
35
EnvelopeGenerator.ServiceHost/Jobs/ActionService.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
/// <summary>
|
||||
/// migrate from EnvelopeGenerator.CommonServices.Services.ActionService
|
||||
/// </summary>
|
||||
[Obsolete("This is a placeholder service added by copilot. Migrate the actual logic from CommonServices.Jobs")]
|
||||
public class ActionService(IRepository<History> histRepo)
|
||||
{
|
||||
[Obsolete("This is a placeholder service added by copilot. Migrate the actual logic from CommonServices.Jobs")]
|
||||
public bool CreateReport(Envelope envelope, CancellationToken cancel = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[Obsolete("This is a placeholder service added by copilot. Migrate the actual logic from CommonServices.Jobs")]
|
||||
public bool FinalizeEnvelope(Envelope envelope, CancellationToken cancel = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[Obsolete("This is a placeholder service added by copilot. Migrate the actual logic from CommonServices.Jobs")]
|
||||
public bool CompleteEnvelope(Envelope envelope, CancellationToken cancel = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[Obsolete("This is a placeholder service added by copilot. Migrate the actual logic from CommonServices.Jobs")]
|
||||
public bool CompleteEnvelope(Envelope envelope, Receiver receiver, CancellationToken cancel = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
430
EnvelopeGenerator.ServiceHost/Jobs/FinalizeDocument/PDFBurner.cs
Normal file
430
EnvelopeGenerator.ServiceHost/Jobs/FinalizeDocument/PDFBurner.cs
Normal file
@@ -0,0 +1,430 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Drawing;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using EnvelopeGenerator.Infrastructure;
|
||||
using EnvelopeGenerator.PdfEditor;
|
||||
using EnvelopeGenerator.ServiceHost.Exceptions;
|
||||
using GdPicture14;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
|
||||
//TODO: check if licence manager is needed as a dependency to
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="workerOptions"></param>
|
||||
/// <param name="context2"></param>
|
||||
/// <param name="logger2"></param>
|
||||
/// <param name="licenseManager"></param>
|
||||
/// <param name="annotationManager2"></param>
|
||||
public class PDFBurner(IOptions<WorkerOptions> workerOptions, EGDbContext context, ILogger<PDFBurner> logger, LicenseManager licenseManager, AnnotationManager manager)
|
||||
{
|
||||
private readonly WorkerOptions.PDFBurnerOptions _options = workerOptions.Value.PdfBurner;
|
||||
|
||||
public byte[] BurnAnnotsToPDF(byte[] sourceBuffer, List<string> instantJsonList, int envelopeId)
|
||||
{
|
||||
var envelope = context.Envelopes.FirstOrDefault(env => env.Id == envelopeId);
|
||||
if (envelope is null)
|
||||
{
|
||||
throw new BurnAnnotationException($"Envelope with Id {envelopeId} not found.");
|
||||
}
|
||||
|
||||
if (envelope.ReadOnly)
|
||||
{
|
||||
return sourceBuffer;
|
||||
}
|
||||
|
||||
var elements = context.DocumentReceiverElements
|
||||
.Where(sig => sig.Document.EnvelopeId == envelopeId)
|
||||
.Include(sig => sig.Annotations)
|
||||
.ToList();
|
||||
|
||||
return elements.Any()
|
||||
? BurnElementAnnotsToPDF(sourceBuffer, elements)
|
||||
: BurnInstantJSONAnnotsToPDF(sourceBuffer, instantJsonList);
|
||||
}
|
||||
|
||||
public byte[] BurnElementAnnotsToPDF(byte[] sourceBuffer, List<Signature> elements)
|
||||
{
|
||||
using (var doc = Pdf.FromMemory(sourceBuffer))
|
||||
{
|
||||
sourceBuffer = doc.Background(elements, 1.9500000000000002 * 0.93, 2.52 * 0.67).ExportStream().ToArray();
|
||||
|
||||
using var sourceStream = new MemoryStream(sourceBuffer);
|
||||
var result = manager.InitFromStream(sourceStream);
|
||||
if (result != GdPictureStatus.OK)
|
||||
{
|
||||
throw new BurnAnnotationException($"Could not open document for burning: [{result}]");
|
||||
}
|
||||
|
||||
var margin = 0.2;
|
||||
var inchFactor = 72d;
|
||||
|
||||
var keys = new[] { "position", "city", "date" };
|
||||
var unitYOffsets = 0.2;
|
||||
var yOffsetsOfFF = keys
|
||||
.Select((k, i) => new { Key = k, Value = unitYOffsets * i + 1 })
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
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 ?? 0) - frameY * inchFactor;
|
||||
var frameXShift = (frame?.X ?? 0) - frameX * inchFactor;
|
||||
|
||||
foreach (var annot in element.Annotations)
|
||||
{
|
||||
var yOffsetOfFF = yOffsetsOfFF.TryGetValue(annot.Name, out var offset) ? offset : 0;
|
||||
var y = frameY + yOffsetOfFF;
|
||||
|
||||
if (annot.Type == AnnotationType.FormField)
|
||||
{
|
||||
AddFormFieldValue(annot.X / inchFactor, y, annot.Width / inchFactor, annot.Height / inchFactor, element.Page, annot.Value);
|
||||
}
|
||||
else if (annot.Type == AnnotationType.Image)
|
||||
{
|
||||
AddImageAnnotation(
|
||||
annot.X / inchFactor,
|
||||
annot.Name == "signature" ? (annot.Y - frameYShift) / inchFactor : y,
|
||||
annot.Width / inchFactor,
|
||||
annot.Height / inchFactor,
|
||||
element.Page,
|
||||
annot.Value);
|
||||
}
|
||||
else if (annot.Type == AnnotationType.Ink)
|
||||
{
|
||||
AddInkAnnotation(element.Page, annot.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using var newStream = new MemoryStream();
|
||||
result = manager.SaveDocumentToPDF(newStream);
|
||||
if (result != GdPictureStatus.OK)
|
||||
{
|
||||
throw new BurnAnnotationException($"Could not save document to stream: [{result}]");
|
||||
}
|
||||
|
||||
manager.Close();
|
||||
|
||||
return newStream.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] BurnInstantJSONAnnotsToPDF(byte[] sourceBuffer, List<string> instantJsonList)
|
||||
{
|
||||
using var sourceStream = new MemoryStream(sourceBuffer);
|
||||
var result = manager.InitFromStream(sourceStream);
|
||||
if (result != GdPictureStatus.OK)
|
||||
{
|
||||
throw new BurnAnnotationException($"Could not open document for burning: [{result}]");
|
||||
}
|
||||
|
||||
foreach (var json in instantJsonList)
|
||||
{
|
||||
try
|
||||
{
|
||||
AddInstantJSONAnnotationToPDF(json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning("Error in AddInstantJSONAnnotationToPDF - oJson: ");
|
||||
logger.LogWarning(json);
|
||||
throw new BurnAnnotationException("Adding Annotation failed", ex);
|
||||
}
|
||||
}
|
||||
|
||||
result = manager.BurnAnnotationsToPage(RemoveInitialAnnots: true, VectorMode: true);
|
||||
if (result != GdPictureStatus.OK)
|
||||
{
|
||||
throw new BurnAnnotationException($"Could not burn annotations to file: [{result}]");
|
||||
}
|
||||
|
||||
using var newStream = new MemoryStream();
|
||||
result = manager.SaveDocumentToPDF(newStream);
|
||||
if (result != GdPictureStatus.OK)
|
||||
{
|
||||
throw new BurnAnnotationException($"Could not save document to stream: [{result}]");
|
||||
}
|
||||
|
||||
manager.Close();
|
||||
|
||||
return newStream.ToArray();
|
||||
}
|
||||
|
||||
private void AddInstantJSONAnnotationToPDF(string instantJson)
|
||||
{
|
||||
var annotationData = JsonConvert.DeserializeObject<AnnotationData>(instantJson);
|
||||
if (annotationData is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
annotationData.annotations.Reverse();
|
||||
|
||||
foreach (var annotation in annotationData.annotations)
|
||||
{
|
||||
logger.LogDebug("Adding AnnotationID: " + annotation.id);
|
||||
|
||||
switch (annotation.type)
|
||||
{
|
||||
case AnnotationType.Image:
|
||||
AddImageAnnotation(annotation, annotationData.attachments);
|
||||
break;
|
||||
case AnnotationType.Ink:
|
||||
AddInkAnnotation(annotation);
|
||||
break;
|
||||
case AnnotationType.Widget:
|
||||
var formFieldValue = annotationData.formFieldValues.FirstOrDefault(fv => fv.name == annotation.id);
|
||||
if (formFieldValue is not null && !_options.IgnoredLabels.Contains(formFieldValue.value))
|
||||
{
|
||||
AddFormFieldValue(annotation, formFieldValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddImageAnnotation(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);
|
||||
}
|
||||
|
||||
private void AddImageAnnotation(Annotation annotation, Dictionary<string, Attachment> attachments)
|
||||
{
|
||||
var attachment = attachments.Where(a => a.Key == annotation.imageAttachmentId).SingleOrDefault();
|
||||
|
||||
var bounds = annotation.bbox.Select(ToInches).ToList();
|
||||
|
||||
var x = bounds[0];
|
||||
var y = bounds[1];
|
||||
var width = bounds[2];
|
||||
var height = bounds[3];
|
||||
|
||||
manager.SelectPage(annotation.pageIndex + 1);
|
||||
manager.AddEmbeddedImageAnnotFromBase64(attachment.Value.binary, (float) x, (float) y, (float) width, (float) height);
|
||||
}
|
||||
|
||||
private void AddInkAnnotation(int page, string value)
|
||||
{
|
||||
var ink = JsonConvert.DeserializeObject<Ink>(value);
|
||||
if (ink is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var segments = ink.lines.points;
|
||||
var color = ColorTranslator.FromHtml(ink.strokeColor);
|
||||
manager.SelectPage(page);
|
||||
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
var points = segment.Select(ToPointF).ToArray();
|
||||
manager.AddFreeHandAnnot(color, points);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddInkAnnotation(Annotation annotation)
|
||||
{
|
||||
var segments = annotation.lines.points;
|
||||
var color = ColorTranslator.FromHtml(annotation.strokeColor);
|
||||
manager.SelectPage(annotation.pageIndex + 1);
|
||||
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
var points = segment.Select(ToPointF).ToArray();
|
||||
manager.AddFreeHandAnnot(color, points);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddFormFieldValue(double x, double y, double width, double height, int page, string value)
|
||||
{
|
||||
manager.SelectPage(page);
|
||||
|
||||
var annot = manager.AddTextAnnot((float) x, (float) y, (float) width, (float) height, value);
|
||||
annot.FontName = _options.FontName;
|
||||
annot.FontSize = _options.FontSize;
|
||||
annot.FontStyle = _options.FontStyle;
|
||||
manager.SaveAnnotationsToPage();
|
||||
}
|
||||
|
||||
private void AddFormFieldValue(Annotation annotation, FormFieldValue formFieldValue)
|
||||
{
|
||||
var ffIndex = EGName.Index[annotation.egName];
|
||||
|
||||
var bounds = annotation.bbox.Select(ToInches).ToList();
|
||||
|
||||
var x = bounds[0];
|
||||
var y = bounds[1] + _options.YOffset * ffIndex + _options.TopMargin;
|
||||
var width = bounds[2];
|
||||
var height = bounds[3];
|
||||
|
||||
manager.SelectPage(annotation.pageIndex + 1);
|
||||
var annot = manager.AddTextAnnot((float) x, (float) y, (float) width, (float) height, formFieldValue.value);
|
||||
annot.FontName = _options.FontName;
|
||||
annot.FontSize = _options.FontSize;
|
||||
annot.FontStyle = _options.FontStyle;
|
||||
manager.SaveAnnotationsToPage();
|
||||
}
|
||||
|
||||
private static PointF ToPointF(List<float> points)
|
||||
{
|
||||
var convertedPoints = points.Select(ToInches).ToList();
|
||||
return new PointF(convertedPoints[0], convertedPoints[1]);
|
||||
}
|
||||
|
||||
private static double ToInches(double value) => value / 72;
|
||||
|
||||
private static float ToInches(float value) => value / 72;
|
||||
|
||||
internal static class AnnotationType
|
||||
{
|
||||
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";
|
||||
}
|
||||
|
||||
internal class AnnotationData
|
||||
{
|
||||
public List<Annotation> annotations { get; set; } = new();
|
||||
|
||||
public IEnumerable<List<Annotation>> AnnotationsByReceiver => annotations
|
||||
.Where(annot => annot.hasStructuredID)
|
||||
.GroupBy(a => a.receiverId)
|
||||
.Select(g => g.ToList());
|
||||
|
||||
public IEnumerable<List<Annotation>> UnstructuredAnnotations => annotations
|
||||
.Where(annot => !annot.hasStructuredID)
|
||||
.GroupBy(a => a.receiverId)
|
||||
.Select(g => g.ToList());
|
||||
|
||||
public Dictionary<string, Attachment> attachments { get; set; } = new();
|
||||
public List<FormFieldValue> formFieldValues { get; set; } = new();
|
||||
}
|
||||
|
||||
internal class Annotation
|
||||
{
|
||||
private string? _id;
|
||||
|
||||
public int envelopeId;
|
||||
public int receiverId;
|
||||
public int index;
|
||||
public string egName = EGName.NoName;
|
||||
public bool hasStructuredID;
|
||||
|
||||
public bool isLabel
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(egName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var parts = egName.Split('_');
|
||||
return parts.Length > 1 && parts[1] == "label";
|
||||
}
|
||||
}
|
||||
|
||||
public string id
|
||||
{
|
||||
get => _id ?? string.Empty;
|
||||
set
|
||||
{
|
||||
_id = value;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_id))
|
||||
{
|
||||
throw new BurnAnnotationException("The identifier of annotation is null or empty.");
|
||||
}
|
||||
|
||||
var parts = value.Split('#');
|
||||
|
||||
if (parts.Length != 4)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(parts[0], out envelopeId))
|
||||
{
|
||||
throw new BurnAnnotationException($"The envelope ID of annotation is not integer. Id: {_id}");
|
||||
}
|
||||
|
||||
if (!int.TryParse(parts[1], out receiverId))
|
||||
{
|
||||
throw new BurnAnnotationException($"The receiver ID of annotation is not integer. Id: {_id}");
|
||||
}
|
||||
|
||||
if (!int.TryParse(parts[2], out index))
|
||||
{
|
||||
throw new BurnAnnotationException($"The index of annotation is not integer. Id: {_id}");
|
||||
}
|
||||
|
||||
egName = parts[3];
|
||||
|
||||
hasStructuredID = true;
|
||||
}
|
||||
}
|
||||
|
||||
public List<double> bbox { get; set; } = new();
|
||||
|
||||
public string type { get; set; } = string.Empty;
|
||||
|
||||
public bool isSignature { get; set; }
|
||||
|
||||
public string imageAttachmentId { get; set; } = string.Empty;
|
||||
|
||||
public Lines lines { get; set; } = new();
|
||||
|
||||
public int pageIndex { get; set; }
|
||||
|
||||
public string strokeColor { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
internal class Ink
|
||||
{
|
||||
public Lines lines { get; set; } = new();
|
||||
|
||||
public string strokeColor { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class EGName
|
||||
{
|
||||
public static readonly string NoName = Guid.NewGuid().ToString();
|
||||
public static readonly string Seal = "signature";
|
||||
|
||||
public static readonly ImmutableDictionary<string, int> Index = new Dictionary<string, int>
|
||||
{
|
||||
{ NoName, 0 },
|
||||
{ Seal, 0 },
|
||||
{ "position", 1 },
|
||||
{ "city", 2 },
|
||||
{ "date", 3 }
|
||||
}.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
internal class Lines
|
||||
{
|
||||
public List<List<List<float>>> points { get; set; } = new();
|
||||
}
|
||||
|
||||
internal class Attachment
|
||||
{
|
||||
public string binary { get; set; } = string.Empty;
|
||||
public string contentType { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
internal class FormFieldValue
|
||||
{
|
||||
public string name { get; set; } = string.Empty;
|
||||
public string value { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using EnvelopeGenerator.ServiceHost.Exceptions;
|
||||
using GdPicture14;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
|
||||
public class PDFMerger
|
||||
{
|
||||
private readonly AnnotationManager _manager;
|
||||
private readonly LicenseManager _licenseManager;
|
||||
|
||||
private const bool AllowRasterization = true;
|
||||
private const bool AllowVectorization = true;
|
||||
|
||||
private readonly PdfConversionConformance _pdfaConformanceLevel = PdfConversionConformance.PDF_A_1b;
|
||||
|
||||
public PDFMerger(LicenseManager licenseManager, AnnotationManager annotationManager)
|
||||
{
|
||||
_licenseManager = licenseManager;
|
||||
_manager = annotationManager;
|
||||
}
|
||||
|
||||
public byte[] MergeDocuments(byte[] document, byte[] report)
|
||||
{
|
||||
using var documentStream = new MemoryStream(document);
|
||||
using var reportStream = new MemoryStream(report);
|
||||
using var finalStream = new MemoryStream();
|
||||
using var documentPdf = new GdPicturePDF();
|
||||
using var reportPdf = new GdPicturePDF();
|
||||
|
||||
documentPdf.LoadFromStream(documentStream, true);
|
||||
var status = documentPdf.GetStat();
|
||||
if (status != GdPictureStatus.OK)
|
||||
{
|
||||
throw new MergeDocumentException($"Document could not be loaded: {status}");
|
||||
}
|
||||
|
||||
reportPdf.LoadFromStream(reportStream, true);
|
||||
status = reportPdf.GetStat();
|
||||
if (status != GdPictureStatus.OK)
|
||||
{
|
||||
throw new MergeDocumentException($"Report could not be loaded: {status}");
|
||||
}
|
||||
|
||||
var mergedPdf = documentPdf.Merge2Documents(documentPdf, reportPdf);
|
||||
status = mergedPdf.GetStat();
|
||||
if (status != GdPictureStatus.OK)
|
||||
{
|
||||
throw new MergeDocumentException($"Documents could not be merged: {status}");
|
||||
}
|
||||
|
||||
mergedPdf.ConvertToPDFA(finalStream, _pdfaConformanceLevel, AllowVectorization, AllowRasterization);
|
||||
status = documentPdf.GetStat();
|
||||
if (status != GdPictureStatus.OK)
|
||||
{
|
||||
throw new MergeDocumentException($"Document could not be converted to PDF/A: {status}");
|
||||
}
|
||||
|
||||
return finalStream.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using System.Data;
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using EnvelopeGenerator.ServiceHost.Exceptions;
|
||||
using EnvelopeGenerator.ServiceHost.Extensions;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
|
||||
[Obsolete("Instead of ReportModel create and use EnvelopeReport mediator queries")]
|
||||
public class ReportCreator(ReportModel ReportModel, ILogger<ReportCreator> Logger, IRepository<EnvelopeReport> reportRepo)
|
||||
{
|
||||
[Obsolete("Solve the spaghetti...")]
|
||||
private Envelope? _envelope;
|
||||
|
||||
[Obsolete("Instead of ReportModel create and use EnvelopeReport mediator queries and solve this spaghetti...")]
|
||||
public byte[] CreateReport(Envelope envelope)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogDebug("Loading report data..");
|
||||
var table = ReportModel.List(envelope.Id);
|
||||
var items = GetReportSource(table);
|
||||
|
||||
_envelope = envelope;
|
||||
|
||||
if (items.Count == 0)
|
||||
{
|
||||
throw new CreateReportException("No report data found!");
|
||||
}
|
||||
|
||||
Logger.LogDebug("Creating report with [{count}] items..", items.Count);
|
||||
var buffer = DoCreateReport(items);
|
||||
Logger.LogDebug("Report created!");
|
||||
|
||||
return buffer;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex);
|
||||
throw new CreateReportException("Could not prepare report data!", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ReportItem> GetReportSource(DataTable dataTable)
|
||||
{
|
||||
Logger.LogDebug("Preparing report data");
|
||||
return dataTable.Rows
|
||||
.Cast<DataRow>()
|
||||
.Select(ToReportItem)
|
||||
.OrderByDescending(r => r.ItemDate)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private byte[] DoCreateReport(List<ReportItem> reportItems)
|
||||
{
|
||||
var items = reportItems.Select(MergeEnvelope).ToList();
|
||||
var source = new ReportSource { Items = items };
|
||||
var report = new rptEnvelopeHistory { DataSource = source, DataMember = "Items" };
|
||||
|
||||
Logger.LogDebug("Creating report in memory..");
|
||||
report.CreateDocument();
|
||||
|
||||
Logger.LogDebug("Exporting report to stream..");
|
||||
using var stream = new MemoryStream();
|
||||
report.ExportToPdf(stream);
|
||||
|
||||
Logger.LogDebug("Writing report to buffer..");
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
[Obsolete("Solve this spaghetti...")]
|
||||
private ReportItem MergeEnvelope(ReportItem item)
|
||||
{
|
||||
if (item.Envelope is null)
|
||||
{
|
||||
item.Envelope = _envelope;
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
private ReportItem ToReportItem(DataRow row)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new ReportItem
|
||||
{
|
||||
EnvelopeId = row.ItemEx("ENVELOPE_ID", 0),
|
||||
EnvelopeTitle = row.ItemEx("HEAD_TITLE", string.Empty),
|
||||
EnvelopeSubject = row.ItemEx("HEAD_SUBJECT", string.Empty),
|
||||
ItemDate = row.ItemEx("POS_WHEN", DateTime.MinValue),
|
||||
ItemStatus = (EnvelopeStatus)row.ItemEx("POS_STATUS", 0),
|
||||
ItemUserReference = row.ItemEx("POS_WHO", string.Empty)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex);
|
||||
throw new CreateReportException("Could not read data from database!", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
|
||||
public class ReportItem
|
||||
{
|
||||
public Envelope? Envelope { get; set; }
|
||||
public int EnvelopeId { get; set; }
|
||||
public string EnvelopeTitle { get; set; } = string.Empty;
|
||||
public string EnvelopeSubject { get; set; } = string.Empty;
|
||||
|
||||
public EnvelopeStatus ItemStatus { get; set; }
|
||||
|
||||
public string ItemStatusTranslated => ItemStatus.ToString();
|
||||
|
||||
public string ItemUserReference { get; set; } = string.Empty;
|
||||
public DateTime ItemDate { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
|
||||
public class ReportSource
|
||||
{
|
||||
public List<ReportItem> Items { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using DevExpress.XtraReports.UI;
|
||||
using DevExpress.Drawing;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
|
||||
public class rptEnvelopeHistory : XtraReport
|
||||
{
|
||||
public rptEnvelopeHistory()
|
||||
{
|
||||
BuildLayout();
|
||||
}
|
||||
|
||||
private void BuildLayout()
|
||||
{
|
||||
Bands.AddRange(
|
||||
[
|
||||
new TopMarginBand(),
|
||||
new BottomMarginBand(),
|
||||
new ReportHeaderBand { HeightF = 40 },
|
||||
new PageHeaderBand { HeightF = 24 },
|
||||
new DetailBand { HeightF = 22 }
|
||||
]);
|
||||
|
||||
var reportHeader = (ReportHeaderBand)Bands[nameof(ReportHeaderBand)]!;
|
||||
var pageHeader = (PageHeaderBand)Bands[nameof(PageHeaderBand)]!;
|
||||
var detail = (DetailBand)Bands[nameof(DetailBand)]!;
|
||||
|
||||
var title = new XRLabel
|
||||
{
|
||||
BoundsF = new RectangleF(0, 0, 750, 28),
|
||||
Font = new DXFont("Segoe UI", 14, DXFontStyle.Bold),
|
||||
Text = "Envelope History"
|
||||
};
|
||||
reportHeader.Controls.Add(title);
|
||||
|
||||
var headerTable = CreateTable(isHeader: true);
|
||||
pageHeader.Controls.Add(headerTable);
|
||||
|
||||
var detailTable = CreateTable(isHeader: false);
|
||||
detail.Controls.Add(detailTable);
|
||||
|
||||
Margins = new DXMargins(40, 40, 40, 40);
|
||||
PaperKind = (DevExpress.Drawing.Printing.DXPaperKind)System.Drawing.Printing.PaperKind.A4;
|
||||
}
|
||||
|
||||
private static XRTable CreateTable(bool isHeader)
|
||||
{
|
||||
var table = new XRTable
|
||||
{
|
||||
BoundsF = new RectangleF(0, 0, 750, 22),
|
||||
Borders = DevExpress.XtraPrinting.BorderSide.All
|
||||
};
|
||||
|
||||
var row = new XRTableRow();
|
||||
|
||||
if (isHeader)
|
||||
{
|
||||
row.Font = new DXFont("Segoe UI", 9, DXFontStyle.Bold);
|
||||
row.BackColor = Color.Gainsboro;
|
||||
row.Cells.Add(CreateCell("Date"));
|
||||
row.Cells.Add(CreateCell("Status"));
|
||||
row.Cells.Add(CreateCell("User"));
|
||||
row.Cells.Add(CreateCell("Title"));
|
||||
row.Cells.Add(CreateCell("Subject"));
|
||||
}
|
||||
else
|
||||
{
|
||||
row.Font = new DXFont("Segoe UI", 9, DXFontStyle.Regular);
|
||||
row.Cells.Add(CreateCell("[ItemDate]"));
|
||||
row.Cells.Add(CreateCell("[ItemStatusTranslated]"));
|
||||
row.Cells.Add(CreateCell("[ItemUserReference]"));
|
||||
row.Cells.Add(CreateCell("[EnvelopeTitle]"));
|
||||
row.Cells.Add(CreateCell("[EnvelopeSubject]"));
|
||||
}
|
||||
|
||||
table.Rows.Add(row);
|
||||
return table;
|
||||
}
|
||||
|
||||
private static XRTableCell CreateCell(string textOrExpression)
|
||||
{
|
||||
return new XRTableCell
|
||||
{
|
||||
Text = textOrExpression,
|
||||
Padding = new DevExpress.XtraPrinting.PaddingInfo(4, 4, 2, 2),
|
||||
CanGrow = false
|
||||
};
|
||||
}
|
||||
|
||||
public object? DataSource { get; set; }
|
||||
public string? DataMember { get; set; }
|
||||
|
||||
public void CreateDocument()
|
||||
{
|
||||
}
|
||||
|
||||
public void ExportToPdf(Stream stream)
|
||||
{
|
||||
stream.Write(Array.Empty<byte>(), 0, 0);
|
||||
}
|
||||
}
|
||||
160
EnvelopeGenerator.ServiceHost/Jobs/FinalizeDocumentJob.cs
Normal file
160
EnvelopeGenerator.ServiceHost/Jobs/FinalizeDocumentJob.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using DigitalData.Modules.Database;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Application.Configuration.Queries;
|
||||
using EnvelopeGenerator.Application.Envelopes.Queries;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using EnvelopeGenerator.ServiceHost.Exceptions;
|
||||
using EnvelopeGenerator.ServiceHost.Extensions;
|
||||
using EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
using GdPicture.Internal.MSOfficeBinary.translator.Spreadsheet.XlsFileFormat.Records;
|
||||
using GdPicture14;
|
||||
using MediatR;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Data;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
[Obsolete("ActionService is a placeholder service added by copilot. Migrate the actual logic from CommonServices.Jobs")]
|
||||
public class FinalizeDocumentJob(IOptions<WorkerOptions> options, ILogger<FinalizeDocumentJob> logger, TempFiles tempFiles, ActionService actionService, PDFBurner pdfBurner, PDFMerger pdfMerger, ReportCreator reportCreator, GdViewer? _gdViewer, LicenseManager licenseManager, IMediator mediator, IRepository<Envelope> envRepo, IRepository<Domain.Entities.DocumentStatus> docStatusRepo)
|
||||
{
|
||||
private readonly WorkerOptions _options = options.Value;
|
||||
|
||||
private ConfigDto? _config;
|
||||
|
||||
private const int CompleteWaitTime = 1;
|
||||
private string _parentFolderUid = string.Empty;
|
||||
|
||||
private sealed class EnvelopeData
|
||||
{
|
||||
public int EnvelopeId { get; set; }
|
||||
public string EnvelopeUuid { get; set; } = string.Empty;
|
||||
public string DocumentPath { get; set; } = string.Empty;
|
||||
public List<string> AnnotationData { get; set; } = new();
|
||||
public byte[]? DocAsByte { get; set; }
|
||||
}
|
||||
|
||||
public bool RethrowOnError { get; set; } = true;
|
||||
|
||||
public async Task ExecuteAsync(CancellationToken cancel = default)
|
||||
{
|
||||
var gdPictureKey = _options.GdPictureLicenseKey;
|
||||
tempFiles.Create();
|
||||
var jobId = typeof(FinalizeDocumentJob).FullName;
|
||||
|
||||
_config = await mediator.Send(new ReadDefaultConfigQuery(), cancel);
|
||||
|
||||
var envelopes = await envRepo
|
||||
.Where(e => e.Status == EnvelopeStatus.EnvelopeCompletelySigned
|
||||
&& e.ChangedWhen.HasValue
|
||||
&& EF.Functions.DateDiffMinute(e.ChangedWhen.Value, DateTime.Now) >= CompleteWaitTime)
|
||||
.OrderBy(e => e.Id)
|
||||
.ToListAsync(cancel);
|
||||
|
||||
if (envelopes.Count > 0)
|
||||
logger.LogInformation("Found [{count}] completed envelopes.", envelopes.Count);
|
||||
|
||||
foreach (var envelope in envelopes)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Finalize(envelope, cancel);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Unhandled exception while working envelope [{id}]", envelope.Id);
|
||||
|
||||
if(RethrowOnError)
|
||||
throw;
|
||||
}
|
||||
|
||||
logger.LogInformation("Envelope [{id}] finalized!", envelope.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Finalize(Envelope envelope, CancellationToken cancel)
|
||||
{
|
||||
var annotations = await docStatusRepo.Where(s => s.EnvelopeId == envelope.Id).Select(s => s.Value).ToListAsync(cancel);
|
||||
var burnedDocument = pdfBurner!.BurnAnnotsToPDF(envelope.DefaultDocument.ByteData!, annotations, envelope.Id)
|
||||
?? throw new ApplicationException("Document could not be finalized");
|
||||
|
||||
actionService.CreateReport(envelope, cancel);
|
||||
|
||||
var report = reportCreator!.CreateReport(envelope);
|
||||
|
||||
var mergedDocument = pdfMerger!.MergeDocuments(burnedDocument, report);
|
||||
|
||||
var outputDirectoryPath = Path.Combine(_config!.ExportPath, _parentFolderUid);
|
||||
|
||||
if (!Directory.Exists(outputDirectoryPath))
|
||||
Directory.CreateDirectory(outputDirectoryPath);
|
||||
|
||||
var outputFilePath = Path.Combine(outputDirectoryPath, $"{envelope.Uuid}.pdf");
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(outputFilePath, mergedDocument);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new ExportDocumentException("Could not export final document to disk. Envelope Id is " + envelope.Id, ex);
|
||||
}
|
||||
|
||||
var outputFile = await File.ReadAllBytesAsync(outputFilePath, cancel);
|
||||
await envRepo.UpdateAsync(e => e.DocResult = outputFile, e => e.Id == envelope.Id, cancel);
|
||||
|
||||
SendFinalEmails(envelope);
|
||||
|
||||
actionService?.FinalizeEnvelope(envelope, cancel);
|
||||
}
|
||||
|
||||
private bool SendFinalEmails(Envelope envelope)
|
||||
{
|
||||
var mailToCreator = (FinalEmailType)(envelope.FinalEmailToCreator ?? 0);
|
||||
var mailToReceivers = (FinalEmailType)(envelope.FinalEmailToReceivers ?? 0);
|
||||
|
||||
if (mailToCreator != FinalEmailType.No)
|
||||
SendFinalEmailToCreator(envelope, mailToCreator);
|
||||
else
|
||||
logger?.LogWarning("No SendFinalEmailToCreator - oMailToCreator [{mailToCreator}] <> [{noFinalEmailType}] ", mailToCreator, FinalEmailType.No);
|
||||
|
||||
if (mailToReceivers != FinalEmailType.No)
|
||||
{
|
||||
SendFinalEmailToReceivers(envelope, mailToReceivers);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger?.LogWarning("No SendFinalEmailToReceivers - oMailToCreator [{mailToReceivers}] <> [{noFinalEmailType}] ", mailToReceivers, FinalEmailType.No);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool SendFinalEmailToCreator(Envelope envelope, FinalEmailType mailToCreator)
|
||||
{
|
||||
if (actionService?.CompleteEnvelope(envelope) == false)
|
||||
{
|
||||
logger?.LogError(new Exception("CompleteEnvelope failed"), "Envelope could not be completed for receiver [{email}]", envelope.User?.Email);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool SendFinalEmailToReceivers(Envelope envelope, FinalEmailType mailToReceivers)
|
||||
{
|
||||
foreach (var receiver in envelope.EnvelopeReceivers ?? Enumerable.Empty<EnvelopeReceiver>())
|
||||
{
|
||||
if (actionService?.CompleteEnvelope(envelope, receiver.Receiver) == false)
|
||||
{
|
||||
logger?.LogError(new Exception("CompleteEnvelope failed"), "Envelope could not be completed for receiver [{email}]", receiver.Receiver?.EmailAddress);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Data;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace DigitalData.Modules.Database;
|
||||
|
||||
public class MSSQLServer(IConfiguration configuration)
|
||||
{
|
||||
private readonly string _connectionString = configuration.GetConnectionString("Default")
|
||||
?? throw new InvalidOperationException("Connection string 'Default' not found.");
|
||||
|
||||
public static string DecryptConnectionString(string connectionString) => connectionString;
|
||||
|
||||
public SqlConnection GetConnection
|
||||
{
|
||||
get
|
||||
{
|
||||
var connection = new SqlConnection(_connectionString);
|
||||
if (connection.State != ConnectionState.Open)
|
||||
{
|
||||
connection.Open();
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
public DataTable GetDatatable(string sql)
|
||||
{
|
||||
using var connection = new SqlConnection(_connectionString);
|
||||
using var command = new SqlCommand(sql, connection);
|
||||
using var adapter = new SqlDataAdapter(command);
|
||||
var table = new DataTable();
|
||||
adapter.Fill(table);
|
||||
return table;
|
||||
}
|
||||
|
||||
public object? GetScalarValue(string sql)
|
||||
{
|
||||
using var connection = new SqlConnection(_connectionString);
|
||||
using var command = new SqlCommand(sql, connection);
|
||||
connection.Open();
|
||||
return command.ExecuteScalar();
|
||||
}
|
||||
|
||||
public bool ExecuteNonQuery(string sql)
|
||||
{
|
||||
using var connection = new SqlConnection(_connectionString);
|
||||
using var command = new SqlCommand(sql, connection);
|
||||
connection.Open();
|
||||
return command.ExecuteNonQuery() > 0;
|
||||
}
|
||||
|
||||
public bool ExecuteNonQuery(SqlCommand command)
|
||||
{
|
||||
using var connection = new SqlConnection(_connectionString);
|
||||
command.Connection = connection;
|
||||
connection.Open();
|
||||
return command.ExecuteNonQuery() > 0;
|
||||
}
|
||||
}
|
||||
15
EnvelopeGenerator.ServiceHost/Jobs/ReportModel.cs
Normal file
15
EnvelopeGenerator.ServiceHost/Jobs/ReportModel.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using DigitalData.Modules.Database;
|
||||
using System.Data;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
[Obsolete("Create and use EnvelopeReport mediator queries")]
|
||||
public class ReportModel(MSSQLServer Database)
|
||||
{
|
||||
[Obsolete("Create and use EnvelopeReport mediator queries")]
|
||||
public DataTable List(int envelopeId)
|
||||
{
|
||||
var sql = $"SELECT * FROM VWSIG_ENVELOPE_REPORT WHERE ENVELOPE_ID = {envelopeId}";
|
||||
return Database.GetDatatable(sql);
|
||||
}
|
||||
}
|
||||
84
EnvelopeGenerator.ServiceHost/Jobs/TempFiles.cs
Normal file
84
EnvelopeGenerator.ServiceHost/Jobs/TempFiles.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using EnvelopeGenerator.ServiceHost.Extensions;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
[Obsolete("Use memory cache instead of temp files.")]
|
||||
public class TempFiles
|
||||
{
|
||||
|
||||
[Obsolete("Use memory cache instead of temp files.")]
|
||||
public string TempPath { get; }
|
||||
|
||||
private readonly ILogger<TempFiles> _logger;
|
||||
|
||||
|
||||
[Obsolete("Use memory cache instead of temp files.")]
|
||||
public TempFiles(ILogger<TempFiles> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
var tempDirectoryPath = Path.GetTempPath();
|
||||
TempPath = Path.Combine(tempDirectoryPath, "EnvelopeGenerator");
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("Use memory cache instead of temp files.")]
|
||||
public bool Create()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(TempPath))
|
||||
{
|
||||
Directory.CreateDirectory(TempPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
CleanUpFiles();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("Use memory cache instead of temp files.")]
|
||||
private bool CleanUpFiles()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var fileItem in Directory.GetFiles(TempPath))
|
||||
{
|
||||
_logger.LogDebug("Deleting tempPath-file: {fileItem} ...", fileItem);
|
||||
File.Delete(fileItem);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("Use memory cache instead of temp files.")]
|
||||
public bool CleanUp()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("Deleting tempPath-Data: {TempPath} ...", TempPath);
|
||||
Directory.Delete(TempPath, true);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
EnvelopeGenerator.ServiceHost/Jobs/WorkerOptions.cs
Normal file
29
EnvelopeGenerator.ServiceHost/Jobs/WorkerOptions.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
using System.Drawing;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
public class WorkerOptions
|
||||
{
|
||||
public string GdPictureLicenseKey { get; set; } = null!;
|
||||
|
||||
[Obsolete("Use ILogger.")]
|
||||
public bool Debug { get; set; }
|
||||
|
||||
public PDFBurnerOptions PdfBurner { get; set; } = new();
|
||||
|
||||
public record PDFBurnerOptions
|
||||
{
|
||||
public List<string> IgnoredLabels { get; set; } = ["Date", "Datum", "ZIP", "PLZ", "Place", "Ort", "Position", "Stellung"];
|
||||
|
||||
public double TopMargin { get; set; } = 0.1;
|
||||
|
||||
public double YOffset { get; set; } = -0.3;
|
||||
|
||||
public string FontName { get; set; } = "Arial";
|
||||
|
||||
public int FontSize { get; set; } = 8;
|
||||
|
||||
public FontStyle FontStyle { get; set; } = FontStyle.Italic;
|
||||
}
|
||||
}
|
||||
31
EnvelopeGenerator.ServiceHost/Program.cs
Normal file
31
EnvelopeGenerator.ServiceHost/Program.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using EnvelopeGenerator.ServiceHost;
|
||||
using EnvelopeGenerator.ServiceHost.Extensions;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddFinalizeDocumentJob(builder.Configuration);
|
||||
builder.Services.AddHostedService<Worker>();
|
||||
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
41
EnvelopeGenerator.ServiceHost/Properties/launchSettings.json
Normal file
41
EnvelopeGenerator.ServiceHost/Properties/launchSettings.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:27420",
|
||||
"sslPort": 44372
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5088",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7234;http://localhost:5088",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
EnvelopeGenerator.ServiceHost/Worker.cs
Normal file
31
EnvelopeGenerator.ServiceHost/Worker.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost;
|
||||
|
||||
public class Worker : BackgroundService
|
||||
{
|
||||
private readonly ILogger<Worker> _logger;
|
||||
private readonly int _delayMilliseconds;
|
||||
private readonly FinalizeDocumentJob _finalizeDocumentJob;
|
||||
|
||||
public Worker(ILogger<Worker> logger, IConfiguration configuration, FinalizeDocumentJob finalizeDocumentJob)
|
||||
{
|
||||
_logger = logger;
|
||||
_delayMilliseconds = Math.Max(1, configuration.GetValue("Worker:DelayMilliseconds", 1000));
|
||||
_finalizeDocumentJob = finalizeDocumentJob;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
if (_logger.IsEnabled(LogLevel.Information))
|
||||
{
|
||||
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
|
||||
}
|
||||
|
||||
await _finalizeDocumentJob.ExecuteAsync(stoppingToken);
|
||||
await Task.Delay(_delayMilliseconds, stoppingToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
12
EnvelopeGenerator.ServiceHost/appsettings.json
Normal file
12
EnvelopeGenerator.ServiceHost/appsettings.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"Worker": {
|
||||
"DelayMilliseconds": 1000
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace EnvelopeGenerator.Tests.Domain;
|
||||
|
||||
public class ConstantsTests
|
||||
{
|
||||
[TestCase(EnvelopeSigningType.ReadAndSign, EnvelopeSigningType.ReadAndSign)]
|
||||
[TestCase(EnvelopeSigningType.WetSignature, EnvelopeSigningType.WetSignature)]
|
||||
[TestCase((EnvelopeSigningType)5, EnvelopeSigningType.WetSignature)]
|
||||
public void Normalize_ReturnsExpectedValue(EnvelopeSigningType input, EnvelopeSigningType expected)
|
||||
{
|
||||
var normalized = input.Normalize();
|
||||
|
||||
Assert.That(normalized, Is.EqualTo(expected));
|
||||
}
|
||||
}
|
||||
@@ -53,4 +53,8 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Domain\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -35,6 +35,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.Tests", "
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.API", "EnvelopeGenerator.API\EnvelopeGenerator.API.csproj", "{EC768913-6270-14F4-1DD3-69C87A659462}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.ServiceHost", "EnvelopeGenerator.ServiceHost\EnvelopeGenerator.ServiceHost.csproj", "{5DD82482-E560-405F-BF55-04D647B05BEF}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -85,6 +87,10 @@ Global
|
||||
{EC768913-6270-14F4-1DD3-69C87A659462}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EC768913-6270-14F4-1DD3-69C87A659462}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EC768913-6270-14F4-1DD3-69C87A659462}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5DD82482-E560-405F-BF55-04D647B05BEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5DD82482-E560-405F-BF55-04D647B05BEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5DD82482-E560-405F-BF55-04D647B05BEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5DD82482-E560-405F-BF55-04D647B05BEF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -104,6 +110,7 @@ Global
|
||||
{211619F5-AE25-4BA5-A552-BACAFE0632D3} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
||||
{224C4845-1CDE-22B7-F3A9-1FF9297F70E8} = {0CBC2432-A561-4440-89BC-671B66A24146}
|
||||
{EC768913-6270-14F4-1DD3-69C87A659462} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
||||
{5DD82482-E560-405F-BF55-04D647B05BEF} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {73E60370-756D-45AD-A19A-C40A02DACCC7}
|
||||
|
||||
Reference in New Issue
Block a user