Compare commits
78 Commits
41dde6f016
...
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 | |||
| 7c7674c822 | |||
| 65f606f573 | |||
| 41e0d4691b | |||
| 64e0a4f749 | |||
| 4cf54d36b9 | |||
| 020cecabf3 | |||
| 40c899e47e | |||
| 0341505f8d | |||
| d4eee1718e | |||
| 9b042d8f45 | |||
|
|
ad0c847172 | ||
| f6d57b1e38 | |||
| b64d2b7478 | |||
|
|
f8c7f60cf9 | ||
| 44edef8ba1 | |||
| 647c5d2353 | |||
| 4ce1d2a370 | |||
| bcc53bf9f1 | |||
| f1e38e3bd3 | |||
| e095860b17 | |||
| 9cfc74aa88 | |||
| 7cd6ca3a5f | |||
| 9b660cb25a | |||
| 3d43d1896d | |||
| bae62c7c08 | |||
| bcc17f6def | |||
| 8e3c334fa3 | |||
| 08299451bb | |||
| 59d6d25bdd | |||
| 9a516ab3c9 | |||
| 8fed342dc5 | |||
| f44643aa3e | |||
| 86c99596c4 | |||
| 2d0c08b2ce | |||
| e0aa963184 | |||
| afa3694cd7 | |||
| 48f4ea0c50 | |||
| 74c4ddda83 | |||
| f9b1e583df | |||
| 7c8e0d8481 | |||
| 6d2ec4cc0b | |||
| 8258d9f43f | |||
| 01f3335238 | |||
| 1d0c758e00 | |||
| 711f7d12e8 | |||
| 43d89699a9 |
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; }
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using DigitalData.UserManager.Application.DTOs.User;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using EnvelopeGenerator.Domain.Interfaces;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
@@ -10,7 +11,7 @@ namespace EnvelopeGenerator.Application.Common.Dto;
|
||||
///
|
||||
/// </summary>
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
public record EnvelopeDto
|
||||
public record EnvelopeDto : IEnvelope
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
@@ -43,6 +44,16 @@ public record EnvelopeDto
|
||||
[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>
|
||||
@@ -59,6 +70,11 @@ public record EnvelopeDto
|
||||
[TemplatePlaceholder("[DOCUMENT_TITLE]")]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Default value is string.Empty
|
||||
/// </summary>
|
||||
public string? Comment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -69,6 +85,21 @@ public record EnvelopeDto
|
||||
/// </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>
|
||||
@@ -89,6 +120,26 @@ public record EnvelopeDto
|
||||
/// </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>
|
||||
|
||||
@@ -29,15 +29,12 @@ public class DocStatusHandler : INotificationHandler<DocSignedNotification>
|
||||
/// <param name="notification"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
public async Task Handle(DocSignedNotification notification, CancellationToken cancel)
|
||||
public Task Handle(DocSignedNotification notification, CancellationToken cancel) => _sender.Send(new CreateDocStatusCommand()
|
||||
{
|
||||
await _sender.Send(new SaveDocStatusCommand()
|
||||
{
|
||||
Envelope = new() { Id = notification.EnvelopeId },
|
||||
Receiver = new() { Id = notification.ReceiverId},
|
||||
Value = notification.PsPdfKitAnnotation is PsPdfKitAnnotation annot
|
||||
? JsonSerializer.Serialize(annot.Instant, Format.Json.ForAnnotations)
|
||||
EnvelopeId = notification.EnvelopeId,
|
||||
ReceiverId = notification.ReceiverId,
|
||||
Value = notification.PsPdfKitAnnotation is PsPdfKitAnnotation annot
|
||||
? JsonSerializer.Serialize(annot.Instant, Format.Json.ForAnnotations)
|
||||
: BlankAnnotationJson
|
||||
}, cancel);
|
||||
}
|
||||
}, cancel);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using DigitalData.EmailProfilerDispatcher.Abstraction.Entities;
|
||||
using EnvelopeGenerator.Application.Common.Configurations;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using Microsoft.Extensions.Options;
|
||||
using EnvelopeGenerator.Domain.Interfaces;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Notifications.DocSigned.Handlers;
|
||||
|
||||
@@ -45,6 +46,25 @@ public class SendSignedMailHandler : SendMailHandler<DocSignedNotification>
|
||||
{ "[DOCUMENT_TITLE]", notification.Envelope?.Title ?? string.Empty },
|
||||
};
|
||||
|
||||
if (notification.Envelope.IsReadAndConfirm())
|
||||
{
|
||||
placeHolders["[SIGNATURE_TYPE]"] = "Lesen und bestätigen";
|
||||
placeHolders["[DOCUMENT_PROCESS]"] = string.Empty;
|
||||
placeHolders["[FINAL_STATUS]"] = "Lesebestätigung";
|
||||
placeHolders["[FINAL_ACTION]"] = "Empfänger bestätigt";
|
||||
placeHolders["[REJECTED_BY_OTHERS]"] = "anderen Empfänger abgelehnt!";
|
||||
placeHolders["[RECEIVER_ACTION]"] = "bestätigt";
|
||||
}
|
||||
else
|
||||
{
|
||||
placeHolders["[SIGNATURE_TYPE]"] = "Signieren";
|
||||
placeHolders["[DOCUMENT_PROCESS]"] = " und elektronisch unterschreiben";
|
||||
placeHolders["[FINAL_STATUS]"] = "Signatur";
|
||||
placeHolders["[FINAL_ACTION]"] = "Vertragspartner unterzeichnet";
|
||||
placeHolders["[REJECTED_BY_OTHERS]"] = "anderen Vertragspartner abgelehnt! Ihre notwendige Unterzeichnung wurde verworfen.";
|
||||
placeHolders["[RECEIVER_ACTION]"] = "unterschrieben";
|
||||
}
|
||||
|
||||
return placeHolders;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,22 @@ namespace EnvelopeGenerator.Application.DocStatus.Commands;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public record CreateDocStatusCommand : ModifyDocStatusCommandBase, IRequest<DocumentStatus>
|
||||
public record CreateDocStatusCommand : IRequest<DocumentStatus>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets timestamp when this record was added. Returns the StatusChangedWhen value.
|
||||
///
|
||||
/// </summary>
|
||||
public DateTime AddedWhen => StatusChangedWhen;
|
||||
public int EnvelopeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int ReceiverId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the display value associated with the status.
|
||||
/// </summary>
|
||||
public string? Value { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
using EnvelopeGenerator.Application.Common.Query;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
|
||||
namespace EnvelopeGenerator.Application.DocStatus.Commands;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public record ModifyDocStatusCommandBase : EnvelopeReceiverQueryBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int? EnvelopeId => Envelope.Id;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int? ReceiverId => Receiver.Id;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public override ReceiverQueryBase Receiver { get => base.Receiver; set => base.Receiver = value; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current status code.
|
||||
/// </summary>
|
||||
public DocumentStatus Status => Value is null ? DocumentStatus.Created : DocumentStatus.Signed;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the display value associated with the status.
|
||||
/// </summary>
|
||||
public string? Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets timestamp when this record was added.
|
||||
/// </summary>
|
||||
public DateTime StatusChangedWhen { get; } = DateTime.Now;
|
||||
|
||||
/// <summary>
|
||||
/// Maps the current command to a new instance of the specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDest"></typeparam>
|
||||
/// <returns></returns>
|
||||
public TDest To<TDest>() where TDest : ModifyDocStatusCommandBase, new()
|
||||
=> new()
|
||||
{
|
||||
Key = Key,
|
||||
Envelope = Envelope,
|
||||
Receiver = Receiver,
|
||||
Value = Value
|
||||
};
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using AutoMapper;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
namespace EnvelopeGenerator.Application.DocStatus.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a command to save the status of a document, either by creating a new status or updating an existing one based on the provided envelope and receiver identifiers.
|
||||
/// It returns the identifier of the saved document status.
|
||||
/// </summary>
|
||||
public record SaveDocStatusCommand : ModifyDocStatusCommandBase, IRequest<DocumentStatusDto?>;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class SaveDocStatusCommandHandler : IRequestHandler<SaveDocStatusCommand, DocumentStatusDto?>
|
||||
{
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
private readonly IRepository<DocumentStatus> _repo;
|
||||
|
||||
private readonly IRepository<Envelope> _envRepo;
|
||||
|
||||
private readonly IRepository<Receiver> _rcvRepo;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="mapper"></param>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="rcvRepo"></param>
|
||||
/// <param name="envRepo"></param>
|
||||
public SaveDocStatusCommandHandler(IMapper mapper, IRepository<DocumentStatus> repo, IRepository<Receiver> rcvRepo, IRepository<Envelope> envRepo)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_repo = repo;
|
||||
_rcvRepo = rcvRepo;
|
||||
_envRepo = envRepo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<DocumentStatusDto?> Handle(SaveDocStatusCommand request, CancellationToken cancel)
|
||||
{
|
||||
// ceck if exists
|
||||
bool isExists = await _repo.ReadOnly().Where(request).AnyAsync(cancel);
|
||||
|
||||
var env = await _envRepo.ReadOnly().Where(request.Envelope).FirstAsync(cancel);
|
||||
var rcv = await _rcvRepo.ReadOnly().Where(request.Receiver).FirstAsync(cancel);
|
||||
|
||||
request.Envelope.Id = env.Id;
|
||||
request.Receiver.Id = rcv.Id;
|
||||
|
||||
if (isExists)
|
||||
{
|
||||
var uReq = request.To<UpdateDocStatusCommand>();
|
||||
await _repo.UpdateAsync(uReq, q => q.Where(request), cancel);
|
||||
}
|
||||
else
|
||||
{
|
||||
var cReq = request.To<CreateDocStatusCommand>();
|
||||
await _repo.CreateAsync(cReq, cancel);
|
||||
}
|
||||
|
||||
var docStatus = await _repo.ReadOnly().Where(request).SingleOrDefaultAsync(cancel);
|
||||
|
||||
return _mapper.Map<DocumentStatusDto>(docStatus);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,41 @@
|
||||
using EnvelopeGenerator.Domain;
|
||||
using EnvelopeGenerator.Application.Common.Commands;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace EnvelopeGenerator.Application.DocStatus.Commands;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public record UpdateDocStatusCommand : ModifyDocStatusCommandBase
|
||||
/// <param name="Value"></param>
|
||||
public record DocStatusUpdateDto(string? Value);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public record UpdateDocStatusCommand : UpdateCommand<DocStatusUpdateDto, DocumentStatus>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets timestamp when this record was added. Returns the StatusChangedWhen value.
|
||||
///
|
||||
/// </summary>
|
||||
public DateTime? ChangedWhen => StatusChangedWhen;
|
||||
public int EnvelopeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int ReceiverId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the display value associated with the status.
|
||||
/// </summary>
|
||||
public string? Value { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override Expression<Func<DocumentStatus, bool>> BuildQueryExpression()
|
||||
{
|
||||
return ds => ds.EnvelopeId == EnvelopeId && ds.ReceiverId == ReceiverId;
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,16 @@ public class MappingProfile : Profile
|
||||
CreateMap<CreateDocStatusCommand, DocumentStatus>()
|
||||
.ForMember(dest => dest.Envelope, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Receiver, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Status, opt => opt.MapFrom(
|
||||
src => src.Value == null
|
||||
? Domain.Constants.DocumentStatus.Created
|
||||
: Domain.Constants.DocumentStatus.Signed))
|
||||
.MapAddedWhen();
|
||||
|
||||
CreateMap<UpdateDocStatusCommand, DocumentStatus>()
|
||||
.ForMember(dest => dest.Envelope, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Receiver, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.StatusChangedWhen, opt => opt.MapFrom(src => DateTime.UtcNow))
|
||||
.MapChangedWhen();
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||
@@ -80,7 +80,7 @@
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.4" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
||||
<PackageReference Include="CommandDotNet">
|
||||
<Version>7.0.5</Version>
|
||||
</PackageReference>
|
||||
@@ -88,7 +88,6 @@
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
|
||||
<PackageReference Include="CommandDotNet">
|
||||
<Version>8.1.1</Version>
|
||||
</PackageReference>
|
||||
@@ -96,7 +95,6 @@
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
|
||||
<PackageReference Include="CommandDotNet">
|
||||
<Version>8.1.1</Version>
|
||||
</PackageReference>
|
||||
|
||||
@@ -34,7 +34,7 @@ public record CreateHistoryCommand : EnvelopeReceiverQueryBase, IRequest<History
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public DateTime AddedWhen { get; } = DateTime.Now;
|
||||
public DateTime AddedWhen { get; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using AutoMapper;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
using EnvelopeGenerator.Application.Histories.Commands;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
@@ -17,6 +18,7 @@ public class MappingProfile: Profile
|
||||
CreateMap<CreateHistoryCommand, History>()
|
||||
.ForMember(dest => dest.Envelope, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Sender, opt => opt.Ignore())
|
||||
.ForMember(dest => dest.Receiver, opt => opt.Ignore());
|
||||
.ForMember(dest => dest.Receiver, opt => opt.Ignore())
|
||||
.MapAddedWhen();
|
||||
}
|
||||
}
|
||||
@@ -190,6 +190,13 @@ public static class Extensions
|
||||
/// <returns></returns>
|
||||
public static string SignDoc(this IStringLocalizer localizer) => localizer[nameof(SignDoc)].Value;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="localizer"></param>
|
||||
/// <returns></returns>
|
||||
public static string ConfirmDoc(this IStringLocalizer localizer) => localizer[nameof(ConfirmDoc)].Value;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -204,6 +211,13 @@ public static class Extensions
|
||||
/// <returns></returns>
|
||||
public static string DocSigned(this IStringLocalizer localizer) => localizer[nameof(DocSigned)].Value;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="localizer"></param>
|
||||
/// <returns></returns>
|
||||
public static string DocConfirmed(this IStringLocalizer localizer) => localizer[nameof(DocConfirmed)].Value;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -239,6 +253,13 @@ public static class Extensions
|
||||
/// <returns></returns>
|
||||
public static string SigAgree(this IStringLocalizer localizer) => localizer[nameof(SigAgree)].Value;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="localizer"></param>
|
||||
/// <returns></returns>
|
||||
public static string ConfirmAgree(this IStringLocalizer localizer) => localizer[nameof(ConfirmAgree)].Value;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -267,6 +288,13 @@ public static class Extensions
|
||||
/// <returns></returns>
|
||||
public static string RejectionInfo1(this IStringLocalizer localizer) => localizer[nameof(RejectionInfo1)].Value;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="localizer"></param>
|
||||
/// <returns></returns>
|
||||
public static string RejectionInfo1ForConfirmation(this IStringLocalizer localizer) => localizer[nameof(RejectionInfo1ForConfirmation)].Value;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
@@ -295,6 +323,13 @@ public static class Extensions
|
||||
/// <returns></returns>
|
||||
public static string SigningProcessTitle(this IStringLocalizer localizer) => localizer[nameof(SigningProcessTitle)].Value;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="localizer"></param>
|
||||
/// <returns></returns>
|
||||
public static string ConfirmationProcessTitle(this IStringLocalizer localizer) => localizer[nameof(ConfirmationProcessTitle)].Value;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
|
||||
@@ -250,7 +250,7 @@
|
||||
<value>Sie können bei Bedarf mit {0}, <a href="mailto:{1}?subject={2}&body=Sehr geehrte(r)%20{0},%0A%0A%0A">{1}</a> Kontakt aufnehmen.</value>
|
||||
</data>
|
||||
<data name="RejectionInfo2_ext" xml:space="preserve">
|
||||
<value>Das Vorgang wurde von einer der beteiligten Parteien abgelehnt. Sie können bei Bedarf mit {0}, <a href="mailto:{1}?subject={2}&body=Sehr geehrte(r)%20{0},%0A%0A%0A">{1}</a> Kontakt aufnehmen.</value>
|
||||
<value>Der Vorgang wurde von einer der beteiligten Parteien abgelehnt. Sie können bei Bedarf mit {0}, <a href="mailto:{1}?subject={2}&body=Sehr geehrte(r)%20{0},%0A%0A%0A">{1}</a> Kontakt aufnehmen.</value>
|
||||
</data>
|
||||
<data name="RejectionReasonQ" xml:space="preserve">
|
||||
<value>Bitte geben Sie einen Grund an:</value>
|
||||
@@ -447,4 +447,34 @@
|
||||
<data name="DocumentReset" xml:space="preserve">
|
||||
<value>Dokument wurde zurückgesetzt.</value>
|
||||
</data>
|
||||
<data name="DocumentSuccessfullyConfirmed" xml:space="preserve">
|
||||
<value>Dokument erfolgreich gelesen und bestätigt!</value>
|
||||
</data>
|
||||
<data name="DocumentConfirmedConfirmationMessage" xml:space="preserve">
|
||||
<value>Sie haben das Dokument gelesen und bestätigt. Im Anschluss erhalten Sie eine schriftliche Bestätigung.</value>
|
||||
</data>
|
||||
<data name="Confirm" xml:space="preserve">
|
||||
<value>Bestätigen</value>
|
||||
</data>
|
||||
<data name="RejectionInfo1Confirmation" xml:space="preserve">
|
||||
<value>Dieser Bestätigungsvorgang wurde abgelehnt!</value>
|
||||
</data>
|
||||
<data name="ConfirmDoc" xml:space="preserve">
|
||||
<value>Dokument bestätigen</value>
|
||||
</data>
|
||||
<data name="DocConfirmed" xml:space="preserve">
|
||||
<value>Dokument bestätigt</value>
|
||||
</data>
|
||||
<data name="ConfirmAgree" xml:space="preserve">
|
||||
<value>Durch Klick auf Abschließen bestätige ich, das Dokument gelesen und zur Kenntnis genommen zu haben.</value>
|
||||
</data>
|
||||
<data name="ConfirmedBy" xml:space="preserve">
|
||||
<value>Bestätigt von</value>
|
||||
</data>
|
||||
<data name="ConfirmationProcessTitle" xml:space="preserve">
|
||||
<value>Titel des Lesebetätigungs-Vorgangs</value>
|
||||
</data>
|
||||
<data name="Confirmations" xml:space="preserve">
|
||||
<value>Bestätigungen</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -447,4 +447,34 @@
|
||||
<data name="DocumentReset" xml:space="preserve">
|
||||
<value>Document has been reset.</value>
|
||||
</data>
|
||||
<data name="DocumentSuccessfullyConfirmed" xml:space="preserve">
|
||||
<value>Document successfully red and confirmed!</value>
|
||||
</data>
|
||||
<data name="DocumentConfirmedConfirmationMessage" xml:space="preserve">
|
||||
<value>You have read and confirmed the document. You will receive a written confirmation afterwards.</value>
|
||||
</data>
|
||||
<data name="Confirm" xml:space="preserve">
|
||||
<value>Confirm</value>
|
||||
</data>
|
||||
<data name="RejectionInfo1Confirmation" xml:space="preserve">
|
||||
<value>This confirmation process has been rejected!</value>
|
||||
</data>
|
||||
<data name="ConfirmDoc" xml:space="preserve">
|
||||
<value>Confirm Document</value>
|
||||
</data>
|
||||
<data name="DocConfirmed" xml:space="preserve">
|
||||
<value>Document confirmed</value>
|
||||
</data>
|
||||
<data name="ConfirmAgree" xml:space="preserve">
|
||||
<value>By clicking on “Complete”, I confirm that I have read and taken note of the document.</value>
|
||||
</data>
|
||||
<data name="ConfirmedBy" xml:space="preserve">
|
||||
<value>Confirmed by</value>
|
||||
</data>
|
||||
<data name="ConfirmationProcessTitle" xml:space="preserve">
|
||||
<value>Title of the read confirmation process</value>
|
||||
</data>
|
||||
<data name="Confirmations" xml:space="preserve">
|
||||
<value>Confirmations</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -447,4 +447,34 @@
|
||||
<data name="DocumentReset" xml:space="preserve">
|
||||
<value>Le document a été réinitialisé.</value>
|
||||
</data>
|
||||
<data name="DocumentSuccessfullyConfirmed" xml:space="preserve">
|
||||
<value>Document lu et confirmé avec succès !</value>
|
||||
</data>
|
||||
<data name="DocumentConfirmedConfirmationMessage" xml:space="preserve">
|
||||
<value>Vous avez lu et confirmé le document. Vous recevrez une confirmation écrite par la suite.</value>
|
||||
</data>
|
||||
<data name="Confirm" xml:space="preserve">
|
||||
<value>Confirmer</value>
|
||||
</data>
|
||||
<data name="RejectionInfo1Confirmation" xml:space="preserve">
|
||||
<value>Cette procédure de confirmation a été rejetée !</value>
|
||||
</data>
|
||||
<data name="ConfirmDoc" xml:space="preserve">
|
||||
<value>Confirmer le document</value>
|
||||
</data>
|
||||
<data name="DocConfirmed" xml:space="preserve">
|
||||
<value>Document confirmé</value>
|
||||
</data>
|
||||
<data name="ConfirmAgree" xml:space="preserve">
|
||||
<value>En cliquant sur « Terminer », je confirme avoir lu et pris connaissance du document.</value>
|
||||
</data>
|
||||
<data name="ConfirmedBy" xml:space="preserve">
|
||||
<value>Confirmé par</value>
|
||||
</data>
|
||||
<data name="ConfirmationProcessTitle" xml:space="preserve">
|
||||
<value>Titre de la procédure de confirmation de lecture</value>
|
||||
</data>
|
||||
<data name="Confirmations" xml:space="preserve">
|
||||
<value>Confirmations</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -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) =>
|
||||
{
|
||||
|
||||
@@ -12,6 +12,7 @@ using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiverReadOnly;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
using MediatR;
|
||||
using EnvelopeGenerator.Domain.Interfaces;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Services;
|
||||
|
||||
@@ -49,14 +50,33 @@ public class EnvelopeMailService : EmailOutService, IEnvelopeMailService
|
||||
_sender = sender;
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, string>> CreatePlaceholders(string? accessCode = null, EnvelopeReceiverDto? envelopeReceiverDto = null)
|
||||
private async Task<Dictionary<string, string>> CreatePlaceholders(string? accessCode = null, EnvelopeReceiverDto? er = null)
|
||||
{
|
||||
if (er!.Envelope.IsReadAndConfirm())
|
||||
{
|
||||
_placeholders["[SIGNATURE_TYPE]"] = "Lesen und bestätigen";
|
||||
_placeholders["[DOCUMENT_PROCESS]"] = string.Empty;
|
||||
_placeholders["[FINAL_STATUS]"] = "Lesebestätigung";
|
||||
_placeholders["[FINAL_ACTION]"] = "Empfänger bestätigt";
|
||||
_placeholders["[REJECTED_BY_OTHERS]"] = "anderen Empfänger abgelehnt!";
|
||||
_placeholders["[RECEIVER_ACTION]"] = "bestätigt";
|
||||
}
|
||||
else
|
||||
{
|
||||
_placeholders["[SIGNATURE_TYPE]"] = "Signieren";
|
||||
_placeholders["[DOCUMENT_PROCESS]"] = " und elektronisch unterschreiben";
|
||||
_placeholders["[FINAL_STATUS]"] = "Signatur";
|
||||
_placeholders["[FINAL_ACTION]"] = "Vertragspartner unterzeichnet";
|
||||
_placeholders["[REJECTED_BY_OTHERS]"] = "anderen Vertragspartner abgelehnt! Ihre notwendige Unterzeichnung wurde verworfen.";
|
||||
_placeholders["[RECEIVER_ACTION]"] = "unterschrieben";
|
||||
}
|
||||
|
||||
if (accessCode is not null)
|
||||
_placeholders["[DOCUMENT_ACCESS_CODE]"] = accessCode;
|
||||
|
||||
if (envelopeReceiverDto?.Envelope is not null && envelopeReceiverDto.Receiver is not null)
|
||||
if (er?.Envelope is not null && er.Receiver is not null)
|
||||
{
|
||||
var erId = (envelopeReceiverDto.Envelope.Uuid, envelopeReceiverDto.Receiver.Signature).ToEnvelopeKey();
|
||||
var erId = (er.Envelope.Uuid, er.Receiver.Signature).ToEnvelopeKey();
|
||||
var sigHost = await _configService.ReadDefaultSignatureHost();
|
||||
var linkToDoc = $"{sigHost}/EnvelopeKey/{erId}";
|
||||
_placeholders["[LINK_TO_DOCUMENT]"] = linkToDoc;
|
||||
@@ -66,7 +86,8 @@ public class EnvelopeMailService : EmailOutService, IEnvelopeMailService
|
||||
return _placeholders;
|
||||
}
|
||||
|
||||
private async Task<Dictionary<string, string>> CreatePlaceholders(EnvelopeReceiverReadOnlyDto? readOnlyDto = null)
|
||||
// TODO: merge the two CreatePlaceholders methods by using a common parameter object containing all the required information to create the place holders.
|
||||
private async Task<Dictionary<string, string>> CreatePlaceholders(EnvelopeReceiverReadOnlyDto? readOnlyDto = null)
|
||||
{
|
||||
if (readOnlyDto?.Envelope is not null && readOnlyDto.Receiver is not null)
|
||||
{
|
||||
@@ -124,7 +145,7 @@ public class EnvelopeMailService : EmailOutService, IEnvelopeMailService
|
||||
return acResult.ToFail<int>().Notice(LogLevel.Error, "Therefore, access code cannot be sent");
|
||||
var accessCode = acResult.Data;
|
||||
|
||||
var placeholders = await CreatePlaceholders(accessCode: accessCode, envelopeReceiverDto: dto);
|
||||
var placeholders = await CreatePlaceholders(accessCode: accessCode, er: dto);
|
||||
|
||||
// Add optional place holders.
|
||||
if (optionalPlaceholders is not null)
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,10 @@ namespace EnvelopeGenerator.Domain.Entities
|
||||
[Column("STATUS")]
|
||||
public Constants.DocumentStatus Status { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("STATUS_CHANGED_WHEN", TypeName = "datetime")]
|
||||
public DateTime? StatusChangedWhen { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("ADDED_WHEN", TypeName = "datetime")]
|
||||
public DateTime AddedWhen { get; set; }
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using Newtonsoft.Json;
|
||||
using EnvelopeGenerator.Domain.Interfaces.Auditing;
|
||||
using EnvelopeGenerator.Domain.Interfaces;
|
||||
#if NETFRAMEWORK
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -13,7 +14,7 @@ using System.Linq;
|
||||
namespace EnvelopeGenerator.Domain.Entities
|
||||
{
|
||||
[Table("TBSIG_ENVELOPE", Schema = "dbo")]
|
||||
public class Envelope : IHasAddedWhen, IHasChangedWhen
|
||||
public class Envelope : IHasAddedWhen, IHasChangedWhen, IEnvelope
|
||||
{
|
||||
public Envelope()
|
||||
{
|
||||
@@ -75,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; }
|
||||
@@ -106,7 +110,8 @@ namespace EnvelopeGenerator.Domain.Entities
|
||||
|
||||
[JsonIgnore]
|
||||
[NotMapped]
|
||||
public bool ReadOnly => EnvelopeTypeId == 2;
|
||||
[Obsolete("Use EnvelopeGenerator.Domain.Interfaces.EnvelopeExtensions.IsReadAndConfirm extension method instead.")]
|
||||
public bool ReadOnly => this.IsReadAndConfirm();
|
||||
|
||||
[Column("CERTIFICATION_TYPE")]
|
||||
public int? CertificationType { get; set; }
|
||||
@@ -149,8 +154,7 @@ namespace EnvelopeGenerator.Domain.Entities
|
||||
#if nullable
|
||||
?
|
||||
#endif
|
||||
Type
|
||||
{ get; set; }
|
||||
Type { get; set; }
|
||||
|
||||
#if NETFRAMEWORK
|
||||
[NotMapped]
|
||||
@@ -164,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; }
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ namespace EnvelopeGenerator.Domain.Entities
|
||||
[Column("ACTION_DATE", TypeName = "datetime")]
|
||||
public DateTime? ActionDate { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public DateTime? ChangedWhen { get => ActionDate; set => ActionDate = value; }
|
||||
|
||||
[Column("COMMENT", TypeName = "nvarchar(max)")]
|
||||
|
||||
15
EnvelopeGenerator.Domain/Interfaces/IEnvelope.cs
Normal file
15
EnvelopeGenerator.Domain/Interfaces/IEnvelope.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace EnvelopeGenerator.Domain.Interfaces
|
||||
{
|
||||
public interface IEnvelope
|
||||
{
|
||||
int? EnvelopeTypeId { get; set; }
|
||||
}
|
||||
|
||||
public static class EnvelopeExtensions
|
||||
{
|
||||
public static bool IsReadAndConfirm(this IEnvelope envelope)
|
||||
{
|
||||
return envelope.EnvelopeTypeId == 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.6.0" />
|
||||
<PackageReference Include="DigitalData.Core.Infrastructure" Version="2.6.1" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="9.0.892" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.4" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.82.1" />
|
||||
<PackageReference Include="QuestPDF" Version="2025.7.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.2.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
@@ -31,7 +31,7 @@
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.4.0" newVersion="4.0.4.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.4.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
@@ -59,7 +59,7 @@
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Extensions.Logging.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-8.0.0.3" newVersion="8.0.0.3" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Text.Encodings.Web" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
@@ -67,7 +67,7 @@
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Extensions.DependencyInjection.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-7.0.0.0" newVersion="7.0.0.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-8.0.0.2" newVersion="8.0.0.2" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Microsoft.Extensions.Caching.Abstractions" publicKeyToken="adb9793829ddae60" culture="neutral" />
|
||||
|
||||
@@ -181,11 +181,11 @@
|
||||
<Reference Include="Microsoft.Extensions.DependencyInjection, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Extensions.DependencyInjection.7.0.0\lib\net462\Microsoft.Extensions.DependencyInjection.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.7.0.0\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
|
||||
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions, Version=8.0.0.2, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.8.0.2\lib\net462\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Extensions.Logging.Abstractions.7.0.0\lib\net462\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
|
||||
<Reference Include="Microsoft.Extensions.Logging.Abstractions, Version=8.0.0.3, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Extensions.Logging.Abstractions.8.0.3\lib\net462\Microsoft.Extensions.Logging.Abstractions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
<package id="Microsoft.Bcl.AsyncInterfaces" version="8.0.0" targetFramework="net48" />
|
||||
<package id="Microsoft.CSharp" version="4.7.0" targetFramework="net48" />
|
||||
<package id="Microsoft.Extensions.DependencyInjection" version="7.0.0" targetFramework="net462" />
|
||||
<package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="7.0.0" targetFramework="net462" />
|
||||
<package id="Microsoft.Extensions.Logging.Abstractions" version="7.0.0" targetFramework="net462" />
|
||||
<package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="8.0.2" targetFramework="net462" />
|
||||
<package id="Microsoft.Extensions.Logging.Abstractions" version="8.0.3" targetFramework="net462" />
|
||||
<package id="Microsoft.VisualBasic" version="10.3.0" targetFramework="net48" />
|
||||
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net48" />
|
||||
<package id="Newtonsoft.Json.Bson" version="1.0.2" targetFramework="net48" />
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<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>
|
||||
|
||||
@@ -18,7 +18,6 @@ public static class DependencyInjection
|
||||
services.AddScoped<PDFBurner>();
|
||||
services.AddScoped<PDFMerger>();
|
||||
services.AddScoped<ReportModel>();
|
||||
services.AddSingleton<State>();
|
||||
services.AddScoped<MSSQLServer>();
|
||||
|
||||
//TODO: Check lifetime of services. They might be singleton or scoped.
|
||||
|
||||
@@ -1,30 +1,34 @@
|
||||
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
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
public bool CompleteEnvelope(Envelope envelope, Receiver receiver, CancellationToken cancel = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
using DigitalData.Modules.Database;
|
||||
using EnvelopeGenerator.ServiceHost.Extensions;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
public class ConfigModel(MSSQLServer Database, ILogger Logger)
|
||||
{
|
||||
public DbConfig LoadConfiguration()
|
||||
{
|
||||
try
|
||||
{
|
||||
const string sql = "SELECT TOP 1 * FROM TBSIG_CONFIG";
|
||||
var table = Database.GetDatatable(sql);
|
||||
var row = table.Rows[0];
|
||||
|
||||
return new DbConfig
|
||||
{
|
||||
DocumentPath = row.ItemEx("DOCUMENT_PATH", string.Empty),
|
||||
DocumentPathOrigin = row.ItemEx("DOCUMENT_PATH", string.Empty),
|
||||
ExportPath = row.ItemEx("EXPORT_PATH", string.Empty),
|
||||
SendingProfile = row.ItemEx("SENDING_PROFILE", 0),
|
||||
SignatureHost = row.ItemEx("SIGNATURE_HOST", string.Empty),
|
||||
ExternalProgramName = row.ItemEx("EXTERNAL_PROGRAM_NAME", string.Empty),
|
||||
Default_Tfa_Enabled = row.ItemEx("DEF_TFA_ENABLED", false),
|
||||
Default_Tfa_WithPhone = row.ItemEx("DEF_TFA_WITH_PHONE", false)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex);
|
||||
return new DbConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
public class DbConfig
|
||||
{
|
||||
public string ExternalProgramName { get; set; } = "signFLOW";
|
||||
public string DocumentPathOrigin { get; set; } = string.Empty;
|
||||
public string DocumentPath { get; set; } = string.Empty;
|
||||
public string ExportPath { get; set; } = string.Empty;
|
||||
public int SendingProfile { get; set; }
|
||||
public string SignatureHost { get; set; } = string.Empty;
|
||||
public bool Default_Tfa_Enabled { get; set; }
|
||||
public bool Default_Tfa_WithPhone { get; set; }
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
using DigitalData.Modules.Database;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using EnvelopeGenerator.ServiceHost.Extensions;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
public class EnvelopeModel(MSSQLServer Database, ILogger Logger)
|
||||
{
|
||||
public Envelope? GetById(int envelopeId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sql = $"SELECT * FROM [dbo].[TBSIG_ENVELOPE] WHERE GUID = {envelopeId}";
|
||||
var table = Database.GetDatatable(sql);
|
||||
var row = table.Rows.Cast<System.Data.DataRow>().SingleOrDefault();
|
||||
if (row is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Envelope
|
||||
{
|
||||
Id = row.ItemEx("GUID", 0),
|
||||
Uuid = row.ItemEx("ENVELOPE_UUID", string.Empty),
|
||||
FinalEmailToCreator = row.ItemEx("FINAL_EMAIL_TO_CREATOR", 0),
|
||||
FinalEmailToReceivers = row.ItemEx("FINAL_EMAIL_TO_RECEIVERS", 0),
|
||||
UserId = row.ItemEx("USER_ID", 0),
|
||||
User = null!,
|
||||
EnvelopeReceivers = new List<EnvelopeReceiver>()
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Data;
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using EnvelopeGenerator.ServiceHost.Exceptions;
|
||||
@@ -7,7 +8,7 @@ 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)
|
||||
public class ReportCreator(ReportModel ReportModel, ILogger<ReportCreator> Logger, IRepository<EnvelopeReport> reportRepo)
|
||||
{
|
||||
[Obsolete("Solve the spaghetti...")]
|
||||
private Envelope? _envelope;
|
||||
|
||||
@@ -1,9 +1,94 @@
|
||||
using DevExpress.XtraReports.UI;
|
||||
using DevExpress.Drawing;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs.FinalizeDocument;
|
||||
|
||||
public class rptEnvelopeHistory
|
||||
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; }
|
||||
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
using System.Data;
|
||||
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.Jobs.FinalizeDocument;
|
||||
using GdPicture14;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Options;
|
||||
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, IConfiguration config, ILogger<FinalizeDocumentJob> logger, TempFiles tempFiles, ActionService actionService, PDFBurner pdfBurner, PDFMerger pdfMerger, ReportCreator reportCreator, ConfigModel _configModel, EnvelopeModel _envelopeModel, ReportModel _reportModel, MSSQLServer _database, GdViewer? _gdViewer, LicenseManager licenseManager)
|
||||
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 DbConfig? _config;
|
||||
private ConfigDto? _config;
|
||||
|
||||
private const int CompleteWaitTime = 1;
|
||||
private string _parentFolderUid = string.Empty;
|
||||
@@ -30,182 +37,78 @@ public class FinalizeDocumentJob(IOptions<WorkerOptions> options, IConfiguration
|
||||
public byte[]? DocAsByte { get; set; }
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(CancellationToken cancellationToken = default)
|
||||
public bool RethrowOnError { get; set; } = true;
|
||||
|
||||
public async Task ExecuteAsync(CancellationToken cancel = default)
|
||||
{
|
||||
var gdPictureKey = _options.GdPictureLicenseKey;
|
||||
tempFiles.Create();
|
||||
var jobId = typeof(FinalizeDocumentJob).FullName;
|
||||
logger.LogDebug("Starting job {jobId}", jobId);
|
||||
|
||||
try
|
||||
_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)
|
||||
{
|
||||
logger.LogDebug("Loading Configuration..");
|
||||
_config = _configModel?.LoadConfiguration() ?? throw new InvalidOperationException("Configuration could not be loaded because there is no record");
|
||||
|
||||
logger.LogDebug("DocumentPath: [{documentPath}]", _config.DocumentPath);
|
||||
logger.LogDebug("ExportPath: [{exportPath}]", _config.ExportPath);
|
||||
|
||||
var completeStatus = EnvelopeStatus.EnvelopeCompletelySigned;
|
||||
var sql = $"SELECT * FROM TBSIG_ENVELOPE WHERE STATUS = {completeStatus} AND DATEDIFF(minute, CHANGED_WHEN, GETDATE()) >= {CompleteWaitTime} ORDER BY GUID";
|
||||
var table = _database.GetDatatable(sql);
|
||||
|
||||
var envelopeIds = table.Rows.Cast<DataRow>()
|
||||
.Select(r => r.Field<int>("GUID"))
|
||||
.ToList();
|
||||
|
||||
if (envelopeIds.Count > 0)
|
||||
try
|
||||
{
|
||||
logger.LogInformation("Found [{count}] completed envelopes.", envelopeIds.Count);
|
||||
await Finalize(envelope, cancel);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Unhandled exception while working envelope [{id}]", envelope.Id);
|
||||
|
||||
if(RethrowOnError)
|
||||
throw;
|
||||
}
|
||||
|
||||
var total = envelopeIds.Count;
|
||||
var current = 1;
|
||||
|
||||
foreach (var id in envelopeIds)
|
||||
{
|
||||
logger.LogInformation("Finalizing Envelope [{id}] ({current}/{total})", id, current, total);
|
||||
try
|
||||
{
|
||||
var envelope = _envelopeModel?.GetById(id);
|
||||
if (envelope is null)
|
||||
{
|
||||
logger.LogWarning("Envelope could not be loaded for Id [{id}]!", id);
|
||||
throw new ArgumentNullException(nameof(EnvelopeData));
|
||||
}
|
||||
|
||||
logger.LogDebug("Loading Envelope Data..");
|
||||
var envelopeData = GetEnvelopeData(id);
|
||||
|
||||
if (envelopeData is null)
|
||||
{
|
||||
logger.LogWarning("EnvelopeData could not be loaded for Id [{id}]!", id);
|
||||
throw new ArgumentNullException(nameof(EnvelopeData));
|
||||
}
|
||||
|
||||
logger.LogDebug("Burning Annotations to pdf ...");
|
||||
var burnedDocument = BurnAnnotationsToPdf(envelopeData);
|
||||
if (burnedDocument is null)
|
||||
{
|
||||
logger.LogWarning("Document could not be finalized!");
|
||||
throw new ApplicationException("Document could not be finalized");
|
||||
}
|
||||
|
||||
if (actionService?.CreateReport(envelope) == false)
|
||||
{
|
||||
logger.LogWarning("Document Report could not be created!");
|
||||
throw new ApplicationException("Document Report could not be created");
|
||||
}
|
||||
|
||||
logger.LogDebug("Creating report..");
|
||||
var report = reportCreator!.CreateReport(envelope);
|
||||
logger.LogDebug("Report created!");
|
||||
|
||||
logger.LogDebug("Merging documents ...");
|
||||
var mergedDocument = pdfMerger!.MergeDocuments(burnedDocument, report);
|
||||
logger.LogDebug("Documents merged!");
|
||||
|
||||
var outputDirectoryPath = Path.Combine(_config.ExportPath, _parentFolderUid);
|
||||
logger.LogDebug("oOutputDirectoryPath is {outputDirectoryPath}", outputDirectoryPath);
|
||||
if (!Directory.Exists(outputDirectoryPath))
|
||||
{
|
||||
logger.LogDebug("Directory not existing. Creating ... ");
|
||||
Directory.CreateDirectory(outputDirectoryPath);
|
||||
}
|
||||
|
||||
var outputFilePath = Path.Combine(outputDirectoryPath, $"{envelope.Uuid}.pdf");
|
||||
logger.LogDebug("Writing finalized Pdf to disk..");
|
||||
logger.LogInformation("Output path is [{outputFilePath}]", outputFilePath);
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllBytes(outputFilePath, mergedDocument);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning("Could not export final document to disk!");
|
||||
throw new ExportDocumentException("Could not export final document to disk!", ex);
|
||||
}
|
||||
|
||||
logger.LogDebug("Writing EB-bytes to database...");
|
||||
UpdateFileDb(outputFilePath, envelope.Id);
|
||||
|
||||
if (!SendFinalEmails(envelope))
|
||||
{
|
||||
throw new ApplicationException("Final emails could not be sent!");
|
||||
}
|
||||
|
||||
logger.LogInformation("Report-mails successfully sent!");
|
||||
|
||||
logger.LogDebug("Setting envelope status..");
|
||||
if (actionService?.FinalizeEnvelope(envelope) == false)
|
||||
{
|
||||
logger.LogWarning("Envelope could not be finalized!");
|
||||
throw new ApplicationException("Envelope could not be finalized");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex);
|
||||
logger.LogWarning(ex, "Unhandled exception while working envelope [{id}]", id);
|
||||
}
|
||||
|
||||
current += 1;
|
||||
logger.LogInformation("Envelope [{id}] finalized!", id);
|
||||
}
|
||||
|
||||
logger.LogDebug("Completed job {jobId} successfully!", jobId);
|
||||
}
|
||||
catch (MergeDocumentException ex)
|
||||
{
|
||||
logger.LogWarning("Certificate Document job failed at step: Merging documents!");
|
||||
logger.LogError(ex);
|
||||
}
|
||||
catch (ExportDocumentException ex)
|
||||
{
|
||||
logger.LogWarning("Certificate Document job failed at step: Exporting document!");
|
||||
logger.LogError(ex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning("Certificate Document job failed!");
|
||||
logger.LogError(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
logger.LogDebug("Job execution for [{jobId}] ended", jobId);
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
private void UpdateFileDb(string filePath, long envelopeId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var imageData = ReadFile(filePath);
|
||||
if (imageData is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var query = $"UPDATE TBSIG_ENVELOPE SET DOC_RESULT = @ImageData WHERE GUID = {envelopeId}";
|
||||
using var command = new SqlCommand(query, _database!.GetConnection);
|
||||
command.Parameters.Add(new SqlParameter("@ImageData", imageData));
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger?.LogError(ex);
|
||||
logger.LogInformation("Envelope [{id}] finalized!", envelope.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[]? ReadFile(string path)
|
||||
private async Task Finalize(Envelope envelope, CancellationToken cancel)
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
var numBytes = fileInfo.Length;
|
||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
using var reader = new BinaryReader(stream);
|
||||
return reader.ReadBytes((int)numBytes);
|
||||
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)
|
||||
@@ -214,23 +117,17 @@ public class FinalizeDocumentJob(IOptions<WorkerOptions> options, IConfiguration
|
||||
var mailToReceivers = (FinalEmailType)(envelope.FinalEmailToReceivers ?? 0);
|
||||
|
||||
if (mailToCreator != FinalEmailType.No)
|
||||
{
|
||||
logger?.LogDebug("Sending email to creator ...");
|
||||
SendFinalEmailToCreator(envelope, mailToCreator);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger?.LogWarning("No SendFinalEmailToCreator - oMailToCreator [{0}] <> [{1}] ", mailToCreator, FinalEmailType.No);
|
||||
}
|
||||
logger?.LogWarning("No SendFinalEmailToCreator - oMailToCreator [{mailToCreator}] <> [{noFinalEmailType}] ", mailToCreator, FinalEmailType.No);
|
||||
|
||||
if (mailToReceivers != FinalEmailType.No)
|
||||
{
|
||||
logger?.LogDebug("Sending emails to receivers..");
|
||||
SendFinalEmailToReceivers(envelope, mailToReceivers);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger?.LogWarning("No SendFinalEmailToReceivers - oMailToCreator [{0}] <> [{1}] ", mailToReceivers, FinalEmailType.No);
|
||||
logger?.LogWarning("No SendFinalEmailToReceivers - oMailToCreator [{mailToReceivers}] <> [{noFinalEmailType}] ", mailToReceivers, FinalEmailType.No);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -238,12 +135,9 @@ public class FinalizeDocumentJob(IOptions<WorkerOptions> options, IConfiguration
|
||||
|
||||
private bool SendFinalEmailToCreator(Envelope envelope, FinalEmailType mailToCreator)
|
||||
{
|
||||
var includeAttachment = SendFinalEmailWithAttachment(mailToCreator);
|
||||
logger?.LogDebug("Attachment included: [{0}]", includeAttachment);
|
||||
|
||||
if (actionService?.CompleteEnvelope(envelope) == false)
|
||||
{
|
||||
logger?.LogError(new Exception("CompleteEnvelope failed"), "Envelope could not be completed for receiver [{0}]", envelope.User?.Email);
|
||||
logger?.LogError(new Exception("CompleteEnvelope failed"), "Envelope could not be completed for receiver [{email}]", envelope.User?.Email);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -252,109 +146,15 @@ public class FinalizeDocumentJob(IOptions<WorkerOptions> options, IConfiguration
|
||||
|
||||
private bool SendFinalEmailToReceivers(Envelope envelope, FinalEmailType mailToReceivers)
|
||||
{
|
||||
var includeAttachment = SendFinalEmailWithAttachment(mailToReceivers);
|
||||
logger?.LogDebug("Attachment included: [{0}]", includeAttachment);
|
||||
|
||||
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 [{0}]", receiver.Receiver?.EmailAddress);
|
||||
logger?.LogError(new Exception("CompleteEnvelope failed"), "Envelope could not be completed for receiver [{email}]", receiver.Receiver?.EmailAddress);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool SendFinalEmailWithAttachment(FinalEmailType type)
|
||||
{
|
||||
return type == FinalEmailType.YesWithAttachment;
|
||||
}
|
||||
|
||||
private byte[] BurnAnnotationsToPdf(EnvelopeData envelopeData)
|
||||
{
|
||||
var envelopeId = envelopeData.EnvelopeId;
|
||||
|
||||
logger?.LogInformation("Burning [{0}] signatures", envelopeData.AnnotationData.Count);
|
||||
var annotations = envelopeData.AnnotationData;
|
||||
var inputPath = string.Empty;
|
||||
if (envelopeData.DocAsByte is null)
|
||||
{
|
||||
inputPath = envelopeData.DocumentPath;
|
||||
logger?.LogInformation("Input path: [{0}]", inputPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger?.LogDebug("we got bytes..");
|
||||
inputPath = _config!.DocumentPathOrigin;
|
||||
logger?.LogDebug("oInputPath: {0}", _config.DocumentPathOrigin);
|
||||
}
|
||||
|
||||
if (envelopeData.DocAsByte is null)
|
||||
{
|
||||
var directorySource = Path.GetDirectoryName(inputPath) ?? string.Empty;
|
||||
var split = directorySource.Split('\\');
|
||||
_parentFolderUid = split[^1];
|
||||
}
|
||||
else
|
||||
{
|
||||
_parentFolderUid = envelopeData.EnvelopeUuid;
|
||||
}
|
||||
|
||||
logger?.LogInformation("ParentFolderUID: [{0}]", _parentFolderUid);
|
||||
byte[] inputDocumentBuffer;
|
||||
if (envelopeData.DocAsByte is not null)
|
||||
{
|
||||
inputDocumentBuffer = envelopeData.DocAsByte;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
inputDocumentBuffer = File.ReadAllBytes(inputPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new BurnAnnotationException("Source document could not be read from disk!", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return pdfBurner!.BurnAnnotsToPDF(inputDocumentBuffer, annotations, envelopeId);
|
||||
}
|
||||
|
||||
private EnvelopeData? GetEnvelopeData(int envelopeId)
|
||||
{
|
||||
var sql = $"SELECT T.GUID, T.ENVELOPE_UUID, T.ENVELOPE_TYPE, T2.FILEPATH, T2.BYTE_DATA FROM [dbo].[TBSIG_ENVELOPE] T\n JOIN TBSIG_ENVELOPE_DOCUMENT T2 ON T.GUID = T2.ENVELOPE_ID\n WHERE T.GUID = {envelopeId}";
|
||||
var table = _database!.GetDatatable(sql);
|
||||
var row = table.Rows.Cast<DataRow>().SingleOrDefault();
|
||||
if (row is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var annotationData = GetAnnotationData(envelopeId);
|
||||
var data = new EnvelopeData
|
||||
{
|
||||
EnvelopeId = envelopeId,
|
||||
DocumentPath = row.ItemEx("FILEPATH", string.Empty),
|
||||
AnnotationData = annotationData,
|
||||
DocAsByte = row.Field<byte[]?>("BYTE_DATA"),
|
||||
EnvelopeUuid = row.ItemEx("ENVELOPE_UUID", string.Empty)
|
||||
};
|
||||
|
||||
logger?.LogDebug("Document path: [{0}]", data.DocumentPath);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private List<string> GetAnnotationData(int envelopeId)
|
||||
{
|
||||
var sql = $"SELECT VALUE FROM TBSIG_DOCUMENT_STATUS WHERE ENVELOPE_ID = {envelopeId}";
|
||||
var table = _database!.GetDatatable(sql);
|
||||
|
||||
return table.Rows.Cast<DataRow>()
|
||||
.Select(r => r.ItemEx("VALUE", string.Empty))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
using DigitalData.Modules.Database;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
|
||||
namespace EnvelopeGenerator.ServiceHost.Jobs;
|
||||
|
||||
[Obsolete("Use DbContext")]
|
||||
public class State
|
||||
{
|
||||
public int UserId { get; set; }
|
||||
public FormUser? User { get; set; }
|
||||
public Config? Config { get; set; }
|
||||
public DbConfig? DbConfig { get; set; }
|
||||
public MSSQLServer? Database { get; set; }
|
||||
}
|
||||
@@ -2,12 +2,17 @@ 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;
|
||||
@@ -15,6 +20,8 @@ public class TempFiles
|
||||
TempPath = Path.Combine(tempDirectoryPath, "EnvelopeGenerator");
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("Use memory cache instead of temp files.")]
|
||||
public bool Create()
|
||||
{
|
||||
try
|
||||
@@ -37,6 +44,8 @@ public class TempFiles
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("Use memory cache instead of temp files.")]
|
||||
private bool CleanUpFiles()
|
||||
{
|
||||
try
|
||||
@@ -56,6 +65,8 @@ public class TempFiles
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("Use memory cache instead of temp files.")]
|
||||
public bool CleanUp()
|
||||
{
|
||||
try
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -91,7 +91,7 @@ public class EnvelopeController : ViewControllerBase
|
||||
if (await _historyService.IsSigned(envelopeId: er.Envelope!.Id, userReference: er.Receiver!.EmailAddress))
|
||||
{
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return View("EnvelopeSigned");
|
||||
return View("EnvelopeSigned", er);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
||||
<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.10.0</Version> <!-- NuGet package version -->
|
||||
<AssemblyVersion>3.10.0.0</AssemblyVersion> <!-- Assembly version for API compatibility -->
|
||||
<FileVersion>3.10.0.0</FileVersion> <!-- Windows file version -->
|
||||
<Version>3.12.0</Version> <!-- NuGet package version -->
|
||||
<AssemblyVersion>3.12.0.0</AssemblyVersion> <!-- Assembly version for API compatibility -->
|
||||
<FileVersion>3.12.0.0</FileVersion> <!-- Windows file version -->
|
||||
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -2093,39 +2093,14 @@
|
||||
<None Include="wwwroot\lib\bootstrap-icons\icons\zoom-out.svg" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.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="9.0.892" />
|
||||
<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="6.4.0" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.1" />
|
||||
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
|
||||
<PackageReference Include="System.DirectoryServices" Version="7.0.1" />
|
||||
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="7.0.1" />
|
||||
<PackageReference Include="System.DirectoryServices.Protocols" Version="7.0.1" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
|
||||
<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="9.0.892" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="7.0.20" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.17" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="8.0.17" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.2.5" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.0" />
|
||||
@@ -2149,8 +2124,8 @@
|
||||
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" />
|
||||
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="3.1.1" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="9.0.892" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="7.0.20" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="9.0.6" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NLog" Version="5.2.5" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.0" />
|
||||
|
||||
@@ -9,12 +9,14 @@
|
||||
@using EnvelopeGenerator.Web.Extensions
|
||||
@using Newtonsoft.Json
|
||||
@using Newtonsoft.Json.Serialization
|
||||
@using EnvelopeGenerator.Domain.Interfaces;
|
||||
@model EnvelopeReceiverDto;
|
||||
@{
|
||||
var envelope = Model.Envelope;
|
||||
var document = Model.Envelope?.Documents?.FirstOrDefault();
|
||||
var sender = Model.Envelope?.User;
|
||||
var isExt = ViewBag.IsExt ?? false;
|
||||
bool IsReadAndConfirm = Model!.Envelope!.IsReadAndConfirm();
|
||||
}
|
||||
<div class="page container p-5">
|
||||
<header class="text-center">
|
||||
@@ -54,7 +56,11 @@
|
||||
c-5.791,5.79-15.176,5.79-20.969,0l-30.32-30.322l-11.676,11.676l30.32,30.32c5.79,5.79,5.79,15.178,0,20.969L299.11,404.045z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1>@(isExt ? _localizer.RejectionInfo1Ext() : _localizer.RejectionInfo1())</h1>
|
||||
<h1>@(isExt
|
||||
? _localizer.RejectionInfo1Ext()
|
||||
: IsReadAndConfirm
|
||||
? _localizer.RejectionInfo1ForConfirmation()
|
||||
: _localizer.RejectionInfo1())</h1>
|
||||
</header>
|
||||
<section class="text-center">
|
||||
<div class="card-body p-0 m-0 ms-4">
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
@{
|
||||
ViewData["Title"] = _localizer.DocSigned();
|
||||
@using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver;
|
||||
@using EnvelopeGenerator.Domain.Interfaces;
|
||||
@model EnvelopeReceiverDto;
|
||||
bool IsReadAndConfirm = Model!.Envelope!.IsReadAndConfirm();
|
||||
ViewData["Title"] = IsReadAndConfirm
|
||||
? _localizer.DocConfirmed()
|
||||
: _localizer.DocSigned();
|
||||
}
|
||||
<div class="page container p-5">
|
||||
<header class="text-center">
|
||||
@@ -9,9 +15,13 @@
|
||||
<path d="M15.354 3.354a.5.5 0 0 0-.708-.708L8 9.293 5.354 6.646a.5.5 0 1 0-.708.708l3 3a.5.5 0 0 0 .708 0l7-7z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1>@_localizer["DocumentSuccessfullySigned"]</h1>
|
||||
<h1>@(IsReadAndConfirm
|
||||
? _localizer["DocumentSuccessfullyConfirmed"]
|
||||
: _localizer["DocumentSuccessfullySigned"])</h1>
|
||||
</header>
|
||||
<section class="text-center">
|
||||
<p>@_localizer["DocumentSignedConfirmationMessage"]</p>
|
||||
<p>@(IsReadAndConfirm
|
||||
? _localizer["DocumentConfirmedConfirmationMessage"]
|
||||
: _localizer["DocumentSignedConfirmationMessage"])</p>
|
||||
</section>
|
||||
</div>
|
||||
@@ -9,6 +9,7 @@
|
||||
@using EnvelopeGenerator.Web.Extensions
|
||||
@using Newtonsoft.Json
|
||||
@using Newtonsoft.Json.Serialization
|
||||
@using EnvelopeGenerator.Domain.Interfaces
|
||||
@model EnvelopeReceiverDto;
|
||||
@{
|
||||
var userCulture = ViewData["UserCulture"] as Culture;
|
||||
@@ -24,7 +25,13 @@
|
||||
if (ViewData["IsReadOnly"] is bool isReadOnly_bool)
|
||||
isReadOnly = isReadOnly_bool;
|
||||
|
||||
ViewData["Title"] = isReadOnly ? _localizer.ViewDoc() : _localizer.SignDoc();
|
||||
var isReadAndConfirm = envelope.IsReadAndConfirm();
|
||||
|
||||
ViewData["Title"] = isReadOnly
|
||||
? _localizer.ViewDoc()
|
||||
: isReadAndConfirm
|
||||
? _localizer.ConfirmDoc()
|
||||
: _localizer.SignDoc();
|
||||
}
|
||||
<div class="envelope-view">
|
||||
@if (!isReadOnly)
|
||||
@@ -62,7 +69,9 @@
|
||||
<div class="progress-container">
|
||||
<div id="signed-count-bar" class="progress"></div>
|
||||
<span class="progress-text">
|
||||
<span id="signed-count">0</span>/<span id="signature-count">@signatureCount</span> @_localizer["Signatures"]
|
||||
<span id="signed-count">0</span>/<span id="signature-count">@signatureCount</span> @(isReadAndConfirm
|
||||
? _localizer["Confirmations"]
|
||||
: _localizer["Signatures"])
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"MailParams": {
|
||||
"Placeholders": {
|
||||
"[NAME_PORTAL]": "signFlow",
|
||||
"[SIGNATURE_TYPE]": "signieren",
|
||||
"[REASON]": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +275,7 @@ async function createAnnotationFrameBlob(receiverName, receiverSignature, timest
|
||||
|
||||
ctx.fillStyle = 'black'
|
||||
ctx.font = `${fontSize * scale}px sans-serif`
|
||||
ctx.fillText(localized.signedBy ?? 'Signed by', 15 * scale, 10 * scale)
|
||||
ctx.fillText((READ_AND_CONFIRM ? localized.confirmedBy : localized.signedBy) ?? 'Signed by', 15 * scale, 10 * scale)
|
||||
ctx.fillText(receiverName, 15 * scale, 60 * scale)
|
||||
ctx.fillText(signatureString, 15 * scale, 70 * scale)
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ class App {
|
||||
|
||||
return Swal.fire({
|
||||
title: localized.confirmation,
|
||||
html: `<div class="text-start fs-6 p-0 m-0">${localized.sigAgree}</div>`,
|
||||
html: `<div class="text-start fs-6 p-0 m-0">${READ_AND_CONFIRM ? localized.confirmAgree : localized.sigAgree}</div>`,
|
||||
icon: "question",
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: "#3085d6",
|
||||
|
||||
2
EnvelopeGenerator.Web/wwwroot/js/app.min.js
vendored
2
EnvelopeGenerator.Web/wwwroot/js/app.min.js
vendored
@@ -1,3 +1,3 @@
|
||||
const formatLocalized=(n,...t)=>typeof n=="string"?n.replace(/\{(\d+)\}/g,(n,i)=>t[i]??""):"";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(){if(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()}),!READ_AND_CONFIRM)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)}READ_AND_CONFIRM||[...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:localized.success,text:localized.documentReset,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(localized.copyLinkSuccess,{alert_type:"success",delay:4,icon_name:"check_circle"})}).catch(function(){bsNotify(localized.copyLinkFailure??localized.unexpectedErrorTitle,{alert_type:"danger",delay:4,icon_name:"error"})});break;case"SHARE":Comp.ShareBackdrop.show();break;case"LOGOUT":await logout()}}async handleFinish(){let n=undefined;if(READ_AND_CONFIRM){const n=JSON.parse(sessionStorage.getItem("pspdf_all_pages_rendered")||"false")===!0;if(!n){const n=JSON.parse(sessionStorage.getItem("pspdf_unviewed_pages")||"[]"),t=n.length?formatLocalized(localized.viewRemainingPages,n.join(", ")):localized.viewAllPages;return await Swal.fire({title:localized.warning,text:t,icon:"warning"}),!1}}else{const i=await this.pdfKit.exportInstantJSON(),r=i.formFieldValues,u=r.filter(n=>isFieldRequired(n)),f=u.some(n=>n.value===undefined||n.value===null||n.value==="");if(f)return Swal.fire({title:localized.warning,text:localized.locationFieldsRequired,icon:"warning"}),!1;const e=new RegExp("^[a-zA-Z\\u0080-\\u024F]+(?:([\\ \\-\\']|(\\.\\ ))[a-zA-Z\\u0080-\\u024F]+)*$"),o=r.filter(n=>isCityField(n));for(var t of o)if(!IS_MOBILE_DEVICE&&!e.test(t.value))return Swal.fire({title:localized.warning,text:formatLocalized(localized.cityFormatInvalid,t.value),icon:"warning"}),!1;const s=await this.validateAnnotations(this.signatureCount);if(s===!1)return Swal.fire({title:localized.warning,text:localized.missingSignatures,icon:"warning"}),!1;n={instant:i,structured:mapSignature(i)}}return 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:localized.warning,text:localized.envelopeSignError,icon:"error"}),!1}try{const t=READ_AND_CONFIRM?await signEnvelope():await signEnvelope(n);if(t.ok)return!0;if(t.status===409)return Swal.fire({title:localized.warning,text:localized.envelopeUnavailable,icon:"warning"}),!1;if(t.status===423)Swal.fire({title:localized.info??localized.warning,text:localized.envelopeRejectedRedirect,icon:"info",timer:2e3,showConfirmButton:!1}).then(()=>{location.reload()});else throw new Error;}catch(i){return Swal.fire({title:localized.warning,text:localized.envelopeSignError,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:localized.resetConfirmTitle,text:localized.resetConfirmText,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(localized.copyLinkSuccess,{alert_type:"success",delay:4,icon_name:"check_circle"})}).catch(function(){bsNotify(localized.copyLinkFailure??localized.unexpectedErrorTitle,{alert_type:"danger",delay:4,icon_name:"error"})});break;case"SHARE":Comp.ShareBackdrop.show();break;case"LOGOUT":await logout()}}async handleFinish(){let n=undefined;if(READ_AND_CONFIRM){const n=JSON.parse(sessionStorage.getItem("pspdf_all_pages_rendered")||"false")===!0;if(!n){const n=JSON.parse(sessionStorage.getItem("pspdf_unviewed_pages")||"[]"),t=n.length?formatLocalized(localized.viewRemainingPages,n.join(", ")):localized.viewAllPages;return await Swal.fire({title:localized.warning,text:t,icon:"warning"}),!1}}else{const i=await this.pdfKit.exportInstantJSON(),r=i.formFieldValues,u=r.filter(n=>isFieldRequired(n)),f=u.some(n=>n.value===undefined||n.value===null||n.value==="");if(f)return Swal.fire({title:localized.warning,text:localized.locationFieldsRequired,icon:"warning"}),!1;const e=new RegExp("^[a-zA-Z\\u0080-\\u024F]+(?:([\\ \\-\\']|(\\.\\ ))[a-zA-Z\\u0080-\\u024F]+)*$"),o=r.filter(n=>isCityField(n));for(var t of o)if(!IS_MOBILE_DEVICE&&!e.test(t.value))return Swal.fire({title:localized.warning,text:formatLocalized(localized.cityFormatInvalid,t.value),icon:"warning"}),!1;const s=await this.validateAnnotations(this.signatureCount);if(s===!1)return Swal.fire({title:localized.warning,text:localized.missingSignatures,icon:"warning"}),!1;n={instant:i,structured:mapSignature(i)}}return Swal.fire({title:localized.confirmation,html:`<div class="text-start fs-6 p-0 m-0">${READ_AND_CONFIRM?localized.confirmAgree: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:localized.warning,text:localized.envelopeSignError,icon:"error"}),!1}try{const t=READ_AND_CONFIRM?await signEnvelope():await signEnvelope(n);if(t.ok)return!0;if(t.status===409)return Swal.fire({title:localized.warning,text:localized.envelopeUnavailable,icon:"warning"}),!1;if(t.status===423)Swal.fire({title:localized.info??localized.warning,text:localized.envelopeRejectedRedirect,icon:"info",timer:2e3,showConfirmButton:!1}).then(()=>{location.reload()});else throw new Error;}catch(i){return Swal.fire({title:localized.warning,text:localized.envelopeSignError,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:localized.resetConfirmTitle,text:localized.resetConfirmText,icon:"question",showCancelButton:!0});if(n.isConfirmed){const n=await deleteAnnotations(this.pdfKit)}return n}fakeElementId=0;}
|
||||
@@ -55,8 +55,8 @@ Global
|
||||
{5E0E17C0-FF5A-4246-BF87-1ADD85376A27}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5E0E17C0-FF5A-4246-BF87-1ADD85376A27}.Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5E0E17C0-FF5A-4246-BF87-1ADD85376A27}.Release|Any CPU.Build.0 = Debug|Any CPU
|
||||
{83ED2617-B398-4859-8F59-B38F8807E83E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{83ED2617-B398-4859-8F59-B38F8807E83E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{83ED2617-B398-4859-8F59-B38F8807E83E}.Debug|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{83ED2617-B398-4859-8F59-B38F8807E83E}.Debug|Any CPU.Build.0 = Release|Any CPU
|
||||
{83ED2617-B398-4859-8F59-B38F8807E83E}.Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{83ED2617-B398-4859-8F59-B38F8807E83E}.Release|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4F32A98D-E6F0-4A09-BD97-1CF26107E837}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
|
||||
Reference in New Issue
Block a user