From 4d2538cc2c2f7abe802c2742026dc56097ce1335 Mon Sep 17 00:00:00 2001 From: TekH Date: Sat, 26 Sep 2026 13:11:28 +0200 Subject: [PATCH] refactor(envelope): consolidate draft flow into upsert command - extend CreateEnvelopeCommand to support draft updates and send intent - merge draft save logic into CreateEnvelopeCommandHandler and remove duplicate handler - add DocReceiverElement writer abstraction and infrastructure registration for SQL-based field upserts --- .../Interfaces/IDocReceiverElementWriter.cs | 10 + .../Commands/CreateEnvelopeCommand.cs | 29 +- .../Commands/CreateEnvelopeCommandHandler.cs | 314 +++++++++++++++++- .../Commands/SaveEnvelopeDraftCommand.cs | 39 --- .../SaveEnvelopeDraftCommandHandler.cs | 268 --------------- .../DependencyInjection.cs | 5 +- .../Writers/DocReceiverElementWriter.cs | 56 ++++ 7 files changed, 402 insertions(+), 319 deletions(-) create mode 100644 EnvelopeGenerator.Application/Common/Interfaces/IDocReceiverElementWriter.cs delete mode 100644 EnvelopeGenerator.Application/Envelopes/Commands/SaveEnvelopeDraftCommand.cs delete mode 100644 EnvelopeGenerator.Application/Envelopes/Commands/SaveEnvelopeDraftCommandHandler.cs create mode 100644 EnvelopeGenerator.Infrastructure/Writers/DocReceiverElementWriter.cs diff --git a/EnvelopeGenerator.Application/Common/Interfaces/IDocReceiverElementWriter.cs b/EnvelopeGenerator.Application/Common/Interfaces/IDocReceiverElementWriter.cs new file mode 100644 index 00000000..50941dee --- /dev/null +++ b/EnvelopeGenerator.Application/Common/Interfaces/IDocReceiverElementWriter.cs @@ -0,0 +1,10 @@ +using EnvelopeGenerator.Domain.Entities; + +namespace EnvelopeGenerator.Application.Common.Interfaces; + +public interface IDocReceiverElementWriter +{ + Task DeleteByDocumentAndReceiverAsync(int documentId, int receiverId, CancellationToken cancellationToken = default); + + Task InsertAsync(DocReceiverElement element, CancellationToken cancellationToken = default); +} diff --git a/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeCommand.cs b/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeCommand.cs index 52bcfeb3..358fbe1c 100644 --- a/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeCommand.cs +++ b/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeCommand.cs @@ -1,5 +1,4 @@ -using DigitalData.Core.Abstraction.Application.Repository; -using EnvelopeGenerator.Application.Common.Dto; +using EnvelopeGenerator.Application.Common.Dto; using EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor; using MediatR; using System.ComponentModel.DataAnnotations; @@ -13,23 +12,29 @@ namespace EnvelopeGenerator.Application.Envelopes.Commands; /// public record CreateEnvelopeCommand : IRequest { + public int? EnvelopeId { get; init; } + /// /// Der Titel des Umschlags. Dies ist ein Pflichtfeld. /// - [Required] - public required string Title { get; set; } + public string? Title { get; set; } /// /// Die Nachricht, die im Umschlag enthalten sein soll. Dies ist ein Pflichtfeld. /// - [Required] - public required string Message { get; set; } + public string? Message { get; set; } /// /// Gibt an, ob die Zwei-Faktor-Authentifizierung für den Umschlag aktiviert ist. Standardmäßig false. /// public bool TFAEnabled { get; set; } = false; + public bool Send { get; init; } + + public CreateEnvelopeDocumentDto? Document { get; init; } + + public List Receivers { get; init; } = new(); + /// /// ID des Absenders /// @@ -55,4 +60,14 @@ public record CreateEnvelopeCommand : IRequest [JsonIgnore] [NotMapped] public bool UseSQLExecutor { get; set; } = true; -} \ No newline at end of file +} + +public record CreateEnvelopeDocumentDto(string DataAsBase64); + +public record CreateEnvelopeFieldDto(double X, double Y, int Page); + +public record CreateEnvelopeReceiverDto( + string EmailAddress, + string? Name, + string? PhoneNumber, + List Fields); diff --git a/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeCommandHandler.cs b/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeCommandHandler.cs index 505f4252..f8d9a4db 100644 --- a/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeCommandHandler.cs +++ b/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeCommandHandler.cs @@ -2,9 +2,15 @@ using MediatR; using Microsoft.Extensions.DependencyInjection; using DigitalData.Core.Abstraction.Application.Repository; +using DigitalData.Core.Exceptions; using EnvelopeGenerator.Domain.Entities; using EnvelopeGenerator.Application.Common.Dto; +using EnvelopeGenerator.Application.Common.Interfaces; using EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor; +using EnvelopeGenerator.Application.Histories.Commands; +using EnvelopeGenerator.Domain.Constants; +using EnvelopeGenerator.Domain; +using Microsoft.EntityFrameworkCore; namespace EnvelopeGenerator.Application.Envelopes.Commands; @@ -19,6 +25,18 @@ public class CreateEnvelopeCommandHandler : IRequestHandler Repository => _provider.GetRequiredService>(); + private IRepository DocumentRepository => _provider.GetRequiredService>(); + + private IRepository EnvelopeReceiverRepository => _provider.GetRequiredService>(); + + private IRepository ReceiverRepository => _provider.GetRequiredService>(); + + private IRepository DocReceiverElementRepository => _provider.GetRequiredService>(); + + private IDocReceiverElementWriter DocReceiverElementWriter => _provider.GetRequiredService(); + + private IMediator Mediator => _provider.GetRequiredService(); + private readonly IMapper _mapper; /// @@ -40,10 +58,298 @@ public class CreateEnvelopeCommandHandler : IRequestHandler public async Task Handle(CreateEnvelopeCommand request, CancellationToken cancel) { - var envelope = request.UseSQLExecutor - ? await Executor.CreateEnvelopeAsync(request.UserId, request.Title, request.Message, request.TFAEnabled, cancel) - : await Repository.CreateAsync(request, cancel); + var now = DateTime.Now; + var title = request.Title?.Trim() ?? string.Empty; + var message = request.Message?.Trim() ?? string.Empty; + + Envelope envelope; + if (request.EnvelopeId is int envelopeId) + { + envelope = await Repository.Query + .Include(e => e.User) + .FirstOrDefaultAsync(e => e.Id == envelopeId, cancel) + ?? throw new BadRequestException($"Envelope {envelopeId} not found."); + + if (envelope.UserId != request.UserId) + throw new ForbiddenException(); + + await Repository.UpdateAsync(e => + { + e.Title = title; + e.Message = message; + e.TfaEnabled = request.TFAEnabled; + e.Status = EnvelopeStatus.EnvelopeSaved; + e.ChangedWhen = now; + }, e => e.Id == envelopeId, cancel); + + envelope.Title = title; + envelope.Message = message; + envelope.TfaEnabled = request.TFAEnabled; + envelope.Status = EnvelopeStatus.EnvelopeSaved; + envelope.ChangedWhen = now; + } + else + { + envelope = request.UseSQLExecutor + ? await Executor.CreateEnvelopeAsync(request.UserId, title, message, request.TFAEnabled, cancel) + : await Repository.CreateAsync(new Envelope + { + UserId = request.UserId, + Status = EnvelopeStatus.EnvelopeCreated, + Uuid = Guid.NewGuid().ToString(), + Title = title, + Message = message, + AddedWhen = now, + ChangedWhen = now, + Comment = string.Empty, + Language = "de-DE", + TfaEnabled = request.TFAEnabled, + UseAccessCode = false, + SendReminderEmails = false + }, cancel); + } + + await UpsertDocumentAsync(envelope.Id, request.Document, now, cancel); + await UpsertReceiversAndFieldsAsync(envelope.Id, request.Receivers, now, cancel); + + if (request.Send) + await SendEnvelopeAsync(envelope, cancel); + + envelope = await Repository.Query + .Include(e => e.User) + .Include(e => e.EnvelopeReceivers) + .ThenInclude(er => er.Receiver) + .FirstAsync(e => e.Id == envelope.Id, cancel); return _mapper.Map(envelope); } -} \ No newline at end of file + + private async Task UpsertDocumentAsync(int envelopeId, CreateEnvelopeDocumentDto? document, DateTime now, CancellationToken cancel) + { + if (document is null || string.IsNullOrWhiteSpace(document.DataAsBase64)) + return; + + byte[] bytes; + try + { + bytes = Convert.FromBase64String(document.DataAsBase64); + } + catch (FormatException) + { + throw new BadRequestException("Document payload is not valid base64."); + } + + var existingDocumentId = await DocumentRepository.Query + .Where(d => d.EnvelopeId == envelopeId) + .Select(d => (int?)d.Id) + .FirstOrDefaultAsync(cancel); + + if (existingDocumentId is null) + { + await DocumentRepository.CreateAsync(new Document + { + EnvelopeId = envelopeId, + AddedWhen = now, + ByteData = bytes, + Filename = string.Empty, + Filepath = string.Empty, + FileNameOriginal = string.Empty + }, cancel); + + return; + } + + await DocumentRepository.UpdateAsync(d => + { + d.ByteData = bytes; + }, d => d.Id == existingDocumentId.Value, cancel); + } + + private async Task UpsertReceiversAndFieldsAsync(int envelopeId, IReadOnlyCollection receivers, DateTime now, CancellationToken cancel) + { + var filteredReceivers = receivers + .Where(r => !string.IsNullOrWhiteSpace(r.EmailAddress)) + .Select(r => r with + { + EmailAddress = r.EmailAddress.Trim().ToLowerInvariant(), + Name = r.Name?.Trim(), + PhoneNumber = string.IsNullOrWhiteSpace(r.PhoneNumber) ? null : r.PhoneNumber.Trim(), + Fields = r.Fields ?? new List() + }) + .GroupBy(r => r.EmailAddress, StringComparer.OrdinalIgnoreCase) + .Select(g => g.First()) + .ToList(); + + if (filteredReceivers.Count == 0) + return; + + var document = await DocumentRepository.Query + .FirstOrDefaultAsync(d => d.EnvelopeId == envelopeId, cancel); + + if (document is null) + return; + + var currentLinks = await EnvelopeReceiverRepository.Query + .Include(er => er.Receiver) + .Where(er => er.EnvelopeId == envelopeId) + .ToListAsync(cancel); + + var linksByEmail = currentLinks + .Where(er => !string.IsNullOrWhiteSpace(er.Receiver?.EmailAddress)) + .GroupBy(er => er.Receiver!.EmailAddress, StringComparer.OrdinalIgnoreCase) + .ToDictionary(g => g.Key.ToLowerInvariant(), g => g.First(), StringComparer.OrdinalIgnoreCase); + + var sequence = currentLinks.Count > 0 ? currentLinks.Max(er => er.Sequence) : 0; + + foreach (var receiverDraft in filteredReceivers) + { + var linkResult = await GetOrCreateEnvelopeReceiverAsync(envelopeId, receiverDraft, linksByEmail, now, sequence, cancel); + var link = linkResult.Link; + sequence = linkResult.NextSequence; + + await DocReceiverElementWriter.DeleteByDocumentAndReceiverAsync(document.Id, link.ReceiverId, cancel); + + foreach (var field in receiverDraft.Fields.Where(f => f.Page > 0)) + { + await DocReceiverElementWriter.InsertAsync(new DocReceiverElement + { + DocumentId = document.Id, + ReceiverId = link.ReceiverId, + ElementType = 1, + X = field.X, + Y = field.Y, + Width = 1.77, + Height = 1.96, + Page = field.Page, + Required = true, + ReadOnly = false, + AnnotationIndex = 0, + AddedWhen = now, + FullName = link.Name, + Position = string.Empty, + Place = string.Empty, + Tooltip = string.Empty + }, cancel); + } + } + } + + private async Task<(EnvelopeReceiver Link, int NextSequence)> GetOrCreateEnvelopeReceiverAsync( + int envelopeId, + CreateEnvelopeReceiverDto receiverDraft, + IDictionary linksByEmail, + DateTime now, + int sequence, + CancellationToken cancel) + { + if (linksByEmail.TryGetValue(receiverDraft.EmailAddress, out var existingLink)) + { + var nextName = string.IsNullOrWhiteSpace(receiverDraft.Name) ? existingLink.Name : receiverDraft.Name!; + var nextPhoneNumber = receiverDraft.PhoneNumber ?? existingLink.PhoneNumber; + + await EnvelopeReceiverRepository.UpdateAsync(er => + { + er.Name = nextName; + er.PhoneNumber = nextPhoneNumber; + er.ChangedWhen = now; + }, er => er.EnvelopeId == existingLink.EnvelopeId && er.ReceiverId == existingLink.ReceiverId, cancel); + + existingLink.Name = nextName; + existingLink.PhoneNumber = nextPhoneNumber; + return (existingLink, sequence); + } + + var receiver = await ReceiverRepository.Query + .FirstOrDefaultAsync(r => r.EmailAddress != null && r.EmailAddress.ToLower() == receiverDraft.EmailAddress, cancel); + + if (receiver is null) + { + receiver = await ReceiverRepository.CreateAsync(new Receiver + { + EmailAddress = receiverDraft.EmailAddress, + Signature = receiverDraft.EmailAddress.ToUpperInvariant().GetChecksum(), + AddedWhen = now, + TotpSecretkey = string.Empty, + TfaRegDeadline = null + }, cancel); + } + + sequence++; + var link = await EnvelopeReceiverRepository.CreateAsync(new EnvelopeReceiver + { + EnvelopeId = envelopeId, + ReceiverId = receiver.Id, + Sequence = sequence, + Name = receiverDraft.Name ?? receiverDraft.EmailAddress, + JobTitle = string.Empty, + CompanyName = string.Empty, + PrivateMessage = string.Empty, + AccessCode = string.Empty, + AddedWhen = now, + ChangedWhen = now, + PhoneNumber = receiverDraft.PhoneNumber ?? string.Empty + }, cancel); + + link.Receiver = receiver; + linksByEmail[receiverDraft.EmailAddress] = link; + return (link, sequence); + } + + private async Task SendEnvelopeAsync(Envelope envelope, CancellationToken cancel) + { + var hasDocument = await DocumentRepository.Query + .AnyAsync(d => d.EnvelopeId == envelope.Id && d.ByteData != null && d.ByteData.Length > 0, cancel); + + if (!hasDocument) + throw new BadRequestException("Cannot send envelope without a document."); + + var links = await EnvelopeReceiverRepository.Query + .Include(er => er.Receiver) + .Where(er => er.EnvelopeId == envelope.Id) + .ToListAsync(cancel); + + if (links.Count == 0) + throw new BadRequestException("Cannot send envelope without receivers."); + + var receiverIds = links.Select(l => l.ReceiverId).Distinct().ToList(); + var documentIds = await DocumentRepository.Query + .Where(d => d.EnvelopeId == envelope.Id) + .Select(d => d.Id) + .ToListAsync(cancel); + + var receiverFieldCounts = await DocReceiverElementRepository.Query + .Where(e => documentIds.Contains(e.DocumentId)) + .Where(e => receiverIds.Contains(e.ReceiverId)) + .GroupBy(e => e.ReceiverId) + .Select(g => new { ReceiverId = g.Key, Count = g.Count() }) + .ToDictionaryAsync(x => x.ReceiverId, x => x.Count, cancel); + + var receiversWithoutFields = links + .Where(link => !receiverFieldCounts.TryGetValue(link.ReceiverId, out var count) || count <= 0) + .Select(link => link.Name) + .ToList(); + + if (receiversWithoutFields.Count > 0) + throw new BadRequestException("Cannot send envelope because some receivers have no signature fields."); + + var changedWhen = DateTime.Now; + await Repository.UpdateAsync(e => + { + e.Status = EnvelopeStatus.EnvelopeQueued; + e.ChangedWhen = changedWhen; + }, e => e.Id == envelope.Id, cancel); + + var userReference = envelope.User?.Email; + if (string.IsNullOrWhiteSpace(userReference)) + throw new BadRequestException("Envelope sender email is missing."); + + await Mediator.Send(new CreateHistoryCommand + { + EnvelopeId = envelope.Id, + UserReference = userReference, + Status = EnvelopeStatus.EnvelopeQueued + }, cancel); + + envelope.Status = EnvelopeStatus.EnvelopeQueued; + } +} diff --git a/EnvelopeGenerator.Application/Envelopes/Commands/SaveEnvelopeDraftCommand.cs b/EnvelopeGenerator.Application/Envelopes/Commands/SaveEnvelopeDraftCommand.cs deleted file mode 100644 index 64c7dc47..00000000 --- a/EnvelopeGenerator.Application/Envelopes/Commands/SaveEnvelopeDraftCommand.cs +++ /dev/null @@ -1,39 +0,0 @@ -using EnvelopeGenerator.Domain.Constants; -using MediatR; - -namespace EnvelopeGenerator.Application.Envelopes.Commands; - -public record SaveEnvelopeDraftResultDto(int EnvelopeId, EnvelopeStatus Status); - -public record SaveEnvelopeDraftCommand : IRequest -{ - public int? EnvelopeId { get; init; } - - public string? Title { get; init; } - - public string? Message { get; init; } - - public bool TFAEnabled { get; init; } - - public SaveEnvelopeDraftDocumentDto? Document { get; init; } - - public List Receivers { get; init; } = new(); - - internal int UserId { get; private set; } - - public SaveEnvelopeDraftCommand WithAuth(int userId) - { - UserId = userId; - return this; - } -} - -public record SaveEnvelopeDraftDocumentDto(string DataAsBase64); - -public record SaveEnvelopeDraftFieldDto(double X, double Y, int Page); - -public record SaveEnvelopeDraftReceiverDto( - string EmailAddress, - string? Name, - string? PhoneNumber, - List Fields); diff --git a/EnvelopeGenerator.Application/Envelopes/Commands/SaveEnvelopeDraftCommandHandler.cs b/EnvelopeGenerator.Application/Envelopes/Commands/SaveEnvelopeDraftCommandHandler.cs deleted file mode 100644 index fd561c20..00000000 --- a/EnvelopeGenerator.Application/Envelopes/Commands/SaveEnvelopeDraftCommandHandler.cs +++ /dev/null @@ -1,268 +0,0 @@ -using DigitalData.Core.Abstraction.Application.Repository; -using DigitalData.Core.Exceptions; -using EnvelopeGenerator.Domain; -using EnvelopeGenerator.Domain.Constants; -using EnvelopeGenerator.Domain.Entities; -using MediatR; -using Microsoft.EntityFrameworkCore; - -namespace EnvelopeGenerator.Application.Envelopes.Commands; - -public class SaveEnvelopeDraftCommandHandler : IRequestHandler -{ - private readonly IRepository _envelopeRepository; - private readonly IRepository _documentRepository; - private readonly IRepository _envelopeReceiverRepository; - private readonly IRepository _receiverRepository; - private readonly IRepository _docReceiverElementRepository; - - public SaveEnvelopeDraftCommandHandler( - IRepository envelopeRepository, - IRepository documentRepository, - IRepository envelopeReceiverRepository, - IRepository receiverRepository, - IRepository docReceiverElementRepository) - { - _envelopeRepository = envelopeRepository; - _documentRepository = documentRepository; - _envelopeReceiverRepository = envelopeReceiverRepository; - _receiverRepository = receiverRepository; - _docReceiverElementRepository = docReceiverElementRepository; - } - - public async Task Handle(SaveEnvelopeDraftCommand request, CancellationToken cancel) - { - var now = DateTime.Now; - var title = request.Title?.Trim() ?? string.Empty; - var message = request.Message?.Trim() ?? string.Empty; - int targetEnvelopeId; - - if (request.EnvelopeId is int envelopeId) - { - var envelopeInfo = await _envelopeRepository.Query - .Where(e => e.Id == envelopeId) - .Select(e => new { e.Id, e.UserId }) - .FirstOrDefaultAsync(cancel) - ?? throw new NotFoundException($"Envelope {envelopeId} not found."); - - if (envelopeInfo.UserId != request.UserId) - throw new ForbiddenException(); - - await _envelopeRepository.UpdateAsync(e => - { - e.Title = title; - e.Message = message; - e.TfaEnabled = request.TFAEnabled; - e.Status = EnvelopeStatus.EnvelopeSaved; - e.ChangedWhen = now; - }, e => e.Id == envelopeId, cancel); - - targetEnvelopeId = envelopeInfo.Id; - } - else - { - var envelope = new Envelope - { - UserId = request.UserId, - Status = EnvelopeStatus.EnvelopeSaved, - Uuid = Guid.NewGuid().ToString(), - Title = title, - Message = message, - AddedWhen = now, - ChangedWhen = now, - Comment = string.Empty, - Language = "de-DE", - TfaEnabled = request.TFAEnabled, - UseAccessCode = false, - SendReminderEmails = false - }; - - envelope = await _envelopeRepository.CreateAsync(envelope, cancel); - targetEnvelopeId = envelope.Id; - } - - await UpsertDocumentAsync(targetEnvelopeId, request.Document, now, cancel); - await UpsertReceiversAndFieldsAsync(targetEnvelopeId, request.Receivers, now, cancel); - - return new SaveEnvelopeDraftResultDto(targetEnvelopeId, EnvelopeStatus.EnvelopeSaved); - } - - private async Task UpsertDocumentAsync(int envelopeId, SaveEnvelopeDraftDocumentDto? document, DateTime now, CancellationToken cancel) - { - if (document is null || string.IsNullOrWhiteSpace(document.DataAsBase64)) - return; - - byte[] bytes; - try - { - bytes = Convert.FromBase64String(document.DataAsBase64); - } - catch (FormatException) - { - throw new BadRequestException("Document payload is not valid base64."); - } - - var existingDocumentId = await _documentRepository.Query - .Where(d => d.EnvelopeId == envelopeId) - .Select(d => (int?)d.Id) - .FirstOrDefaultAsync(cancel); - - if (existingDocumentId is null) - { - await _documentRepository.CreateAsync(new Document - { - EnvelopeId = envelopeId, - AddedWhen = now, - ByteData = bytes, - Filename = string.Empty, - Filepath = string.Empty, - FileNameOriginal = string.Empty - }, cancel); - - return; - } - - await _documentRepository.UpdateAsync(d => - { - d.ByteData = bytes; - }, d => d.Id == existingDocumentId.Value, cancel); - } - - private async Task UpsertReceiversAndFieldsAsync(int envelopeId, IReadOnlyCollection receivers, DateTime now, CancellationToken cancel) - { - var filteredReceivers = receivers - .Where(r => !string.IsNullOrWhiteSpace(r.EmailAddress)) - .Select(r => r with - { - EmailAddress = r.EmailAddress.Trim().ToLowerInvariant(), - Name = r.Name?.Trim(), - PhoneNumber = string.IsNullOrWhiteSpace(r.PhoneNumber) ? null : r.PhoneNumber.Trim(), - Fields = r.Fields ?? new List() - }) - .GroupBy(r => r.EmailAddress, StringComparer.OrdinalIgnoreCase) - .Select(g => g.First()) - .ToList(); - - if (filteredReceivers.Count == 0) - return; - - var document = await _documentRepository.Query - .FirstOrDefaultAsync(d => d.EnvelopeId == envelopeId, cancel); - - if (document is null) - return; - - var currentLinks = await _envelopeReceiverRepository.Query - .Include(er => er.Receiver) - .Where(er => er.EnvelopeId == envelopeId) - .ToListAsync(cancel); - - var linksByReceiverId = currentLinks.ToDictionary(er => er.ReceiverId, er => er); - var linksByEmail = currentLinks - .Where(er => !string.IsNullOrWhiteSpace(er.Receiver?.EmailAddress)) - .GroupBy(er => er.Receiver!.EmailAddress, StringComparer.OrdinalIgnoreCase) - .ToDictionary(g => g.Key.ToLowerInvariant(), g => g.First(), StringComparer.OrdinalIgnoreCase); - - var sequence = currentLinks.Count > 0 ? currentLinks.Max(er => er.Sequence) : 0; - - foreach (var receiverDraft in filteredReceivers) - { - var linkResult = await GetOrCreateEnvelopeReceiverAsync(envelopeId, receiverDraft, linksByEmail, linksByReceiverId, now, sequence, cancel); - var link = linkResult.Link; - sequence = linkResult.NextSequence; - - await _docReceiverElementRepository.DeleteAsync(query => query - .Where(e => e.DocumentId == document.Id) - .Where(e => e.ReceiverId == link.ReceiverId), cancel); - - foreach (var field in receiverDraft.Fields.Where(f => f.Page > 0)) - { - await _docReceiverElementRepository.CreateAsync(new DocReceiverElement - { - DocumentId = document.Id, - ReceiverId = link.ReceiverId, - ElementType = 1, - X = field.X, - Y = field.Y, - Width = 1.77, - Height = 1.96, - Page = field.Page, - Required = true, - ReadOnly = false, - AnnotationIndex = 0, - AddedWhen = now, - FullName = link.Name, - Position = string.Empty, - Place = string.Empty, - Tooltip = string.Empty - }, cancel); - } - } - } - - private async Task<(EnvelopeReceiver Link, int NextSequence)> GetOrCreateEnvelopeReceiverAsync( - int envelopeId, - SaveEnvelopeDraftReceiverDto receiverDraft, - IDictionary linksByEmail, - IDictionary linksByReceiverId, - DateTime now, - int sequence, - CancellationToken cancel) - { - if (linksByEmail.TryGetValue(receiverDraft.EmailAddress, out var existingLink)) - { - var nextName = string.IsNullOrWhiteSpace(receiverDraft.Name) ? existingLink.Name : receiverDraft.Name!; - var nextPhoneNumber = receiverDraft.PhoneNumber ?? existingLink.PhoneNumber; - - existingLink.Name = nextName; - existingLink.PhoneNumber = nextPhoneNumber; - existingLink.ChangedWhen = now; - - await _envelopeReceiverRepository.UpdateAsync(er => - { - er.Name = nextName; - er.PhoneNumber = nextPhoneNumber; - er.ChangedWhen = now; - }, er => er.EnvelopeId == existingLink.EnvelopeId && er.ReceiverId == existingLink.ReceiverId, cancel); - return (existingLink, sequence); - } - - var receiver = await _receiverRepository.Query - .FirstOrDefaultAsync(r => r.EmailAddress != null && r.EmailAddress.ToLower() == receiverDraft.EmailAddress, cancel); - - if (receiver is null) - { - receiver = await _receiverRepository.CreateAsync(new Receiver - { - EmailAddress = receiverDraft.EmailAddress, - Signature = receiverDraft.EmailAddress.ToUpperInvariant().GetChecksum(), - AddedWhen = now, - TotpSecretkey = string.Empty, - TfaRegDeadline = null - }, cancel); - } - - sequence++; - var link = new EnvelopeReceiver - { - EnvelopeId = envelopeId, - ReceiverId = receiver.Id, - Sequence = sequence, - Name = receiverDraft.Name ?? receiverDraft.EmailAddress, - JobTitle = string.Empty, - CompanyName = string.Empty, - PrivateMessage = string.Empty, - AccessCode = string.Empty, - AddedWhen = now, - ChangedWhen = now, - PhoneNumber = receiverDraft.PhoneNumber ?? string.Empty - }; - - link = await _envelopeReceiverRepository.CreateAsync(link, cancel); - link.Receiver = receiver; - - linksByReceiverId[link.ReceiverId] = link; - linksByEmail[receiverDraft.EmailAddress] = link; - return (link, sequence); - } -} diff --git a/EnvelopeGenerator.Infrastructure/DependencyInjection.cs b/EnvelopeGenerator.Infrastructure/DependencyInjection.cs index 560aa938..97129709 100644 --- a/EnvelopeGenerator.Infrastructure/DependencyInjection.cs +++ b/EnvelopeGenerator.Infrastructure/DependencyInjection.cs @@ -12,9 +12,11 @@ using DigitalData.UserManager.Domain.Entities; using CommandDotNet.Execution; using EnvelopeGenerator.Infrastructure.Repositories; using EnvelopeGenerator.Infrastructure.Executor; +using EnvelopeGenerator.Infrastructure.Writers; using EnvelopeGenerator.Application.Common.Interfaces.Repositories; using EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor; using EnvelopeGenerator.Application.Common.Interfaces.Services; +using EnvelopeGenerator.Application.Common.Interfaces; using DigitalData.EmailProfilerDispatcher.Abstraction.Entities; #elif NETFRAMEWORK using System; @@ -56,6 +58,7 @@ namespace EnvelopeGenerator.Infrastructure services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); + services.TryAddScoped(); // User Preferences — distributed cache (dbo.TBDD_CACHE) services.TryAddScoped(); @@ -214,4 +217,4 @@ namespace EnvelopeGenerator.Infrastructure #endif } } -} \ No newline at end of file +} diff --git a/EnvelopeGenerator.Infrastructure/Writers/DocReceiverElementWriter.cs b/EnvelopeGenerator.Infrastructure/Writers/DocReceiverElementWriter.cs new file mode 100644 index 00000000..c06ecf64 --- /dev/null +++ b/EnvelopeGenerator.Infrastructure/Writers/DocReceiverElementWriter.cs @@ -0,0 +1,56 @@ +#if NET +using EnvelopeGenerator.Application.Common.Interfaces; +using EnvelopeGenerator.Domain.Entities; +using Microsoft.EntityFrameworkCore; + +namespace EnvelopeGenerator.Infrastructure.Writers; + +public class DocReceiverElementWriter(EGDbContext dbContext) : IDocReceiverElementWriter +{ + public Task DeleteByDocumentAndReceiverAsync(int documentId, int receiverId, CancellationToken cancellationToken = default) + { + return dbContext.Database.ExecuteSqlInterpolatedAsync( + $"DELETE FROM [dbo].[TBSIG_DOCUMENT_RECEIVER_ELEMENT] WHERE [DOCUMENT_ID] = {documentId} AND [RECEIVER_ID] = {receiverId}", + cancellationToken); + } + + public Task InsertAsync(DocReceiverElement element, CancellationToken cancellationToken = default) + { + return dbContext.Database.ExecuteSqlInterpolatedAsync($@" +INSERT INTO [dbo].[TBSIG_DOCUMENT_RECEIVER_ELEMENT] + ([DOCUMENT_ID] + ,[RECEIVER_ID] + ,[ELEMENT_TYPE] + ,[POSITION_X] + ,[POSITION_Y] + ,[WIDTH] + ,[HEIGHT] + ,[REQUIRED] + ,[READ_ONLY] + ,[PAGE] + ,[ADDED_WHEN] + ,[FULL_NAME] + ,[POSITION] + ,[PLACE] + ,[TOOLTIP] + ,[ANNOTATION_INDEX]) + VALUES ({element.DocumentId} + ,{element.ReceiverId} + ,{element.ElementType} + ,{element.X} + ,{element.Y} + ,{element.Width} + ,{element.Height} + ,{element.Required} + ,{element.ReadOnly} + ,{element.Page} + ,{element.AddedWhen} + ,{element.FullName} + ,{element.Position} + ,{element.Place} + ,{element.Tooltip} + ,{element.AnnotationIndex})", + cancellationToken); + } +} +#endif