Compare commits
3 Commits
435be882c9
...
b46c3c8ebe
| Author | SHA1 | Date | |
|---|---|---|---|
| b46c3c8ebe | |||
| 9f712a6b8c | |||
| 4449743105 |
@@ -27,6 +27,8 @@ namespace EnvelopeGenerator.API.Controllers;
|
|||||||
[Authorize]
|
[Authorize]
|
||||||
public class EnvelopeController : ControllerBase
|
public class EnvelopeController : ControllerBase
|
||||||
{
|
{
|
||||||
|
public record DeleteEnvelopeRequest(string Reason);
|
||||||
|
|
||||||
private readonly ILogger<EnvelopeController> _logger;
|
private readonly ILogger<EnvelopeController> _logger;
|
||||||
private readonly IMediator _mediator;
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
@@ -108,4 +110,17 @@ public class EnvelopeController : ControllerBase
|
|||||||
else
|
else
|
||||||
return Ok(res);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize(AuthenticationSchemes = AuthScheme.Sender)]
|
||||||
|
[HttpDelete("{id:int}")]
|
||||||
|
public async Task<IActionResult> DeleteAsync([FromRoute] int id, [FromBody] DeleteEnvelopeRequest request)
|
||||||
|
{
|
||||||
|
var result = await _mediator.Send(new DeleteEnvelopeCommand
|
||||||
|
{
|
||||||
|
EnvelopeId = id,
|
||||||
|
Reason = request?.Reason ?? string.Empty
|
||||||
|
}.WithAuth(User.GetId()));
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -69,6 +69,11 @@ public class ExceptionHandlingMiddleware
|
|||||||
message = notFoundEx.Message;
|
message = notFoundEx.Message;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ForbiddenException forbiddenEx:
|
||||||
|
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
message = forbiddenEx.Message;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.LogError(exception, "Unhandled exception occurred.");
|
logger.LogError(exception, "Unhandled exception occurred.");
|
||||||
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
|
using EnvelopeGenerator.Domain.Entities;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Application.Common.Notifications.EnvelopeDeleted;
|
||||||
|
|
||||||
|
public record EnvelopeDeletedNotification : INotification, ISendMailNotification
|
||||||
|
{
|
||||||
|
public required Envelope Envelope { get; init; }
|
||||||
|
|
||||||
|
public required EnvelopeReceiver EnvelopeReceiver { get; init; }
|
||||||
|
|
||||||
|
public required string Reason { get; init; }
|
||||||
|
|
||||||
|
public required string SenderReference { get; init; }
|
||||||
|
|
||||||
|
public EmailTemplateType TemplateType => EmailTemplateType.DocumentDeleted;
|
||||||
|
|
||||||
|
public string EmailAddress => EnvelopeReceiver.Receiver?.EmailAddress
|
||||||
|
?? throw new InvalidOperationException("Receiver email is missing for EnvelopeDeletedNotification.");
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using DigitalData.Core.Abstraction.Application.Repository;
|
||||||
|
using DigitalData.EmailProfilerDispatcher.Abstraction.Entities;
|
||||||
|
using EnvelopeGenerator.Application.Common.Configurations;
|
||||||
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
|
using EnvelopeGenerator.Domain.Entities;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Application.Common.Notifications.EnvelopeDeleted.Handlers;
|
||||||
|
|
||||||
|
public class SendDeletedMailHandler : SendMailHandler<EnvelopeDeletedNotification>
|
||||||
|
{
|
||||||
|
public SendDeletedMailHandler(
|
||||||
|
IRepository<EmailTemplate> tempRepo,
|
||||||
|
IRepository<EmailOut> emailOutRepo,
|
||||||
|
IOptions<MailParams> mailParamsOptions,
|
||||||
|
IOptions<DispatcherParams> dispatcherParamsOptions)
|
||||||
|
: base(tempRepo, emailOutRepo, mailParamsOptions, dispatcherParamsOptions)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ConfigureEmailOut(EnvelopeDeletedNotification notification, EmailOut emailOut)
|
||||||
|
{
|
||||||
|
emailOut.ReferenceString = notification.EmailAddress;
|
||||||
|
emailOut.ReferenceId = notification.EnvelopeReceiver.ReceiverId;
|
||||||
|
emailOut.WfId = (int)EnvelopeStatus.MessageDeletionSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Dictionary<string, string> CreatePlaceHolders(EnvelopeDeletedNotification notification)
|
||||||
|
{
|
||||||
|
var senderName = notification.Envelope.User?.Email;
|
||||||
|
if (string.IsNullOrWhiteSpace(senderName))
|
||||||
|
senderName = notification.SenderReference;
|
||||||
|
|
||||||
|
return new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["[NAME_RECEIVER]"] = notification.EnvelopeReceiver.Name ?? string.Empty,
|
||||||
|
["[NAME_SENDER]"] = senderName ?? string.Empty,
|
||||||
|
["[DOCUMENT_TITLE]"] = notification.Envelope.Title ?? string.Empty,
|
||||||
|
["[REASON]"] = notification.Reason
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Application.Envelopes.Commands;
|
||||||
|
|
||||||
|
public record DeleteEnvelopeResultDto(int EnvelopeId, EnvelopeStatus AppliedStatus, bool IsNoOp);
|
||||||
|
|
||||||
|
public record DeleteEnvelopeCommand : IRequest<DeleteEnvelopeResultDto>
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public int EnvelopeId { get; init; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MinLength(1)]
|
||||||
|
public string Reason { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
internal int UserId { get; private set; }
|
||||||
|
|
||||||
|
public DeleteEnvelopeCommand WithAuth(int userId)
|
||||||
|
{
|
||||||
|
UserId = userId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using EnvelopeGenerator.Application.Common.Notifications.EnvelopeDeleted;
|
||||||
|
using DigitalData.Core.Abstraction.Application.Repository;
|
||||||
|
using DigitalData.Core.Exceptions;
|
||||||
|
using EnvelopeGenerator.Application.Histories.Commands;
|
||||||
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
|
using EnvelopeGenerator.Domain.Entities;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Application.Envelopes.Commands;
|
||||||
|
|
||||||
|
public class DeleteEnvelopeCommandHandler : IRequestHandler<DeleteEnvelopeCommand, DeleteEnvelopeResultDto>
|
||||||
|
{
|
||||||
|
private readonly IRepository<Envelope> _envelopeRepository;
|
||||||
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
|
public DeleteEnvelopeCommandHandler(
|
||||||
|
IRepository<Envelope> envelopeRepository,
|
||||||
|
IMediator mediator)
|
||||||
|
{
|
||||||
|
_envelopeRepository = envelopeRepository;
|
||||||
|
_mediator = mediator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DeleteEnvelopeResultDto> Handle(DeleteEnvelopeCommand request, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
var reason = request.Reason?.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(reason))
|
||||||
|
throw new BadRequestException("Please provide a reason.");
|
||||||
|
|
||||||
|
var envelope = await _envelopeRepository.Query
|
||||||
|
.Include(e => e.User)
|
||||||
|
.Include(e => e.EnvelopeReceivers)
|
||||||
|
.ThenInclude(er => er.Receiver)
|
||||||
|
.FirstOrDefaultAsync(e => e.Id == request.EnvelopeId, cancel)
|
||||||
|
?? throw new NotFoundException($"Envelope {request.EnvelopeId} not found.");
|
||||||
|
|
||||||
|
if (envelope.UserId != request.UserId)
|
||||||
|
throw new ForbiddenException();
|
||||||
|
|
||||||
|
if (envelope.Status is EnvelopeStatus.EnvelopeDeleted or EnvelopeStatus.EnvelopeWithdrawn)
|
||||||
|
return new DeleteEnvelopeResultDto(envelope.Id, envelope.Status, IsNoOp: true);
|
||||||
|
|
||||||
|
var targetStatus = envelope.Status > EnvelopeStatus.EnvelopeSaved
|
||||||
|
? EnvelopeStatus.EnvelopeWithdrawn
|
||||||
|
: EnvelopeStatus.EnvelopeDeleted;
|
||||||
|
|
||||||
|
envelope.Status = targetStatus;
|
||||||
|
envelope.Comment = reason;
|
||||||
|
envelope.ChangedWhen = DateTime.Now;
|
||||||
|
|
||||||
|
await _envelopeRepository.UpdateAsync(envelope, 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 = targetStatus,
|
||||||
|
Comment = reason
|
||||||
|
}, cancel);
|
||||||
|
|
||||||
|
foreach (var envelopeReceiver in envelope.EnvelopeReceivers ?? Enumerable.Empty<EnvelopeReceiver>())
|
||||||
|
{
|
||||||
|
if (envelopeReceiver.Receiver is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
await _mediator.Publish(new EnvelopeDeletedNotification
|
||||||
|
{
|
||||||
|
Envelope = envelope,
|
||||||
|
EnvelopeReceiver = envelopeReceiver,
|
||||||
|
Reason = reason,
|
||||||
|
SenderReference = userReference
|
||||||
|
}, cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DeleteEnvelopeResultDto(envelope.Id, targetStatus, IsNoOp: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@ namespace EnvelopeGenerator.Server.Client.Services;
|
|||||||
public class EnvelopeService(IHttpClientFactory clientFactory)
|
public class EnvelopeService(IHttpClientFactory clientFactory)
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);
|
private static readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);
|
||||||
|
private sealed record DeleteEnvelopeRequest(string Reason);
|
||||||
|
private sealed record ErrorResponse(string? Message);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fetches envelopes from the API with optional filters.
|
/// Fetches envelopes from the API with optional filters.
|
||||||
@@ -61,4 +63,35 @@ public class EnvelopeService(IHttpClientFactory clientFactory)
|
|||||||
|
|
||||||
return await response.Content.ReadFromJsonAsync<IEnumerable<EnvelopeDto>>(_jsonOptions, cancel);
|
return await response.Content.ReadFromJsonAsync<IEnumerable<EnvelopeDto>>(_jsonOptions, cancel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task DeleteAsync(int envelopeId, string reason, CancellationToken cancel = default)
|
||||||
|
{
|
||||||
|
var httpClient = clientFactory.CreateClient("EnvelopeGenerator.Server");
|
||||||
|
using var request = new HttpRequestMessage(HttpMethod.Delete, $"/api/Envelope/{envelopeId}")
|
||||||
|
{
|
||||||
|
Content = JsonContent.Create(new DeleteEnvelopeRequest(reason), options: _jsonOptions)
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await httpClient.SendAsync(request, cancel);
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string? message = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var error = await response.Content.ReadFromJsonAsync<ErrorResponse>(_jsonOptions, cancel);
|
||||||
|
message = error?.Message;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
message = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var statusCode = (int)response.StatusCode;
|
||||||
|
var reasonPhrase = response.ReasonPhrase ?? "Unknown error";
|
||||||
|
throw new HttpRequestException(
|
||||||
|
$"Failed to delete envelope. Status: {statusCode} ({reasonPhrase}){(string.IsNullOrWhiteSpace(message) ? string.Empty : $" - {message}")}",
|
||||||
|
null,
|
||||||
|
response.StatusCode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -521,6 +521,54 @@
|
|||||||
</BodyContentTemplate>
|
</BodyContentTemplate>
|
||||||
</DxPopup>
|
</DxPopup>
|
||||||
|
|
||||||
|
<DxPopup @bind-Visible="@_deletePopupVisible"
|
||||||
|
HeaderText="Umschlag löschen"
|
||||||
|
Width="560px"
|
||||||
|
MaxWidth="95vw"
|
||||||
|
ShowFooter="true"
|
||||||
|
CloseOnOutsideClick="false"
|
||||||
|
ShowCloseButton="true"
|
||||||
|
CloseOnEscape="true"
|
||||||
|
ShowHeader="true"
|
||||||
|
CssClass="sender-delete-popup">
|
||||||
|
<BodyContentTemplate>
|
||||||
|
<div style="display:flex;flex-direction:column;gap:0.75rem;">
|
||||||
|
<p style="margin:0;color:#374151;font-size:0.9rem;line-height:1.45;">
|
||||||
|
Bitte geben Sie einen Grund an, warum der Umschlag gelöscht/zurückgezogen werden soll.
|
||||||
|
</p>
|
||||||
|
@if (_selectedEnvelope is not null)
|
||||||
|
{
|
||||||
|
<p style="margin:0;color:#6b7280;font-size:0.825rem;">
|
||||||
|
Umschlag: <strong>#@_selectedEnvelope.Id - @_selectedEnvelope.Title</strong>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
<DxMemo @bind-Text="_deleteReason"
|
||||||
|
NullText="Begründung eingeben..."
|
||||||
|
Rows="5"
|
||||||
|
MaxLength="2000"
|
||||||
|
CssClass="w-100" />
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_deleteErrorMessage))
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger" style="margin:0;padding:0.5rem 0.75rem;font-size:0.85rem;">
|
||||||
|
@_deleteErrorMessage
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</BodyContentTemplate>
|
||||||
|
<FooterContentTemplate>
|
||||||
|
<div style="display:flex;justify-content:flex-end;gap:0.5rem;width:100%;">
|
||||||
|
<DxButton Text="Abbrechen"
|
||||||
|
RenderStyle="ButtonRenderStyle.Secondary"
|
||||||
|
Click="CloseDeletePopup"
|
||||||
|
Enabled="@(!_isDeletingEnvelope)" />
|
||||||
|
<DxButton Text="Löschen"
|
||||||
|
RenderStyle="ButtonRenderStyle.Danger"
|
||||||
|
Click="ConfirmDeleteEnvelopeAsync"
|
||||||
|
Enabled="@(!_isDeletingEnvelope)" />
|
||||||
|
</div>
|
||||||
|
</FooterContentTemplate>
|
||||||
|
</DxPopup>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private IEnumerable<EnvelopeDto>? _allEnvelopes;
|
private IEnumerable<EnvelopeDto>? _allEnvelopes;
|
||||||
private IEnumerable<EnvelopeDto>? _activeEnvelopes;
|
private IEnumerable<EnvelopeDto>? _activeEnvelopes;
|
||||||
@@ -549,6 +597,11 @@
|
|||||||
private bool _exportSelectedRowsOnly = false;
|
private bool _exportSelectedRowsOnly = false;
|
||||||
private bool _isExporting = false;
|
private bool _isExporting = false;
|
||||||
|
|
||||||
|
private bool _deletePopupVisible = false;
|
||||||
|
private bool _isDeletingEnvelope = false;
|
||||||
|
private string _deleteReason = string.Empty;
|
||||||
|
private string? _deleteErrorMessage;
|
||||||
|
|
||||||
// Stable keys used to store/restore each grid's layout in UserPreferences.GridLayouts
|
// Stable keys used to store/restore each grid's layout in UserPreferences.GridLayouts
|
||||||
private const string GridKeyActive = "sender.active-envelopes";
|
private const string GridKeyActive = "sender.active-envelopes";
|
||||||
private const string GridKeyCompleted = "sender.completed-envelopes";
|
private const string GridKeyCompleted = "sender.completed-envelopes";
|
||||||
@@ -681,7 +734,51 @@
|
|||||||
void DeleteEnvelope()
|
void DeleteEnvelope()
|
||||||
{
|
{
|
||||||
if (_selectedEnvelope == null) return;
|
if (_selectedEnvelope == null) return;
|
||||||
// TODO: Show delete confirmation dialog
|
_deleteReason = string.Empty;
|
||||||
|
_deleteErrorMessage = null;
|
||||||
|
_deletePopupVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CloseDeletePopup()
|
||||||
|
{
|
||||||
|
if (_isDeletingEnvelope)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_deletePopupVisible = false;
|
||||||
|
_deleteErrorMessage = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async Task ConfirmDeleteEnvelopeAsync()
|
||||||
|
{
|
||||||
|
if (_selectedEnvelope is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(_deleteReason))
|
||||||
|
{
|
||||||
|
_deleteErrorMessage = "Bitte geben Sie einen Löschgrund an.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isDeletingEnvelope = true;
|
||||||
|
_deleteErrorMessage = null;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await EnvelopeService.DeleteAsync(_selectedEnvelope.Id, _deleteReason.Trim());
|
||||||
|
_deletePopupVisible = false;
|
||||||
|
_selectedEnvelope = null;
|
||||||
|
await LoadEnvelopesAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_deleteErrorMessage = ex.Message;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_isDeletingEnvelope = false;
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async Task LogoutAsync()
|
async Task LogoutAsync()
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ namespace EnvelopeGenerator.Server.Controllers;
|
|||||||
[Authorize]
|
[Authorize]
|
||||||
public class EnvelopeController : ControllerBase
|
public class EnvelopeController : ControllerBase
|
||||||
{
|
{
|
||||||
|
public record DeleteEnvelopeRequest(string Reason);
|
||||||
|
|
||||||
private readonly ILogger<EnvelopeController> _logger;
|
private readonly ILogger<EnvelopeController> _logger;
|
||||||
private readonly IMediator _mediator;
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
@@ -108,4 +110,17 @@ public class EnvelopeController : ControllerBase
|
|||||||
else
|
else
|
||||||
return Ok(res);
|
return Ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Authorize(AuthenticationSchemes = AuthScheme.Sender)]
|
||||||
|
[HttpDelete("{id:int}")]
|
||||||
|
public async Task<IActionResult> DeleteAsync([FromRoute] int id, [FromBody] DeleteEnvelopeRequest request)
|
||||||
|
{
|
||||||
|
var result = await _mediator.Send(new DeleteEnvelopeCommand
|
||||||
|
{
|
||||||
|
EnvelopeId = id,
|
||||||
|
Reason = request?.Reason ?? string.Empty
|
||||||
|
}.WithAuth(User.GetId()));
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -69,6 +69,11 @@ public class ExceptionHandlingMiddleware
|
|||||||
message = notFoundEx.Message;
|
message = notFoundEx.Message;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ForbiddenException forbiddenEx:
|
||||||
|
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
message = forbiddenEx.Message;
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logger.LogError(exception, "Unhandled exception occurred.");
|
logger.LogError(exception, "Unhandled exception occurred.");
|
||||||
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||||
|
|||||||
@@ -186,7 +186,7 @@
|
|||||||
},
|
},
|
||||||
"DbTriggerParams": {
|
"DbTriggerParams": {
|
||||||
"Envelope": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ],
|
"Envelope": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ],
|
||||||
"EnvelopeHistory": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ],
|
"History": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ],
|
||||||
"EmailOut": [ "TBEMLP_EMAIL_OUT_AFT_INS", "TBEMLP_EMAIL_OUT_AFT_UPD" ],
|
"EmailOut": [ "TBEMLP_EMAIL_OUT_AFT_INS", "TBEMLP_EMAIL_OUT_AFT_UPD" ],
|
||||||
"EnvelopeReceiverReadOnly": [ "TBSIG_ENVELOPE_RECEIVER_READ_ONLY_UPD" ],
|
"EnvelopeReceiverReadOnly": [ "TBSIG_ENVELOPE_RECEIVER_READ_ONLY_UPD" ],
|
||||||
"Receiver": [],
|
"Receiver": [],
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
using DigitalData.Core.Exceptions;
|
||||||
|
using DigitalData.EmailProfilerDispatcher.Abstraction.Entities;
|
||||||
|
using EnvelopeGenerator.Application.Envelopes.Commands;
|
||||||
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
|
using EnvelopeGenerator.Domain.Entities;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Tests.Application;
|
||||||
|
|
||||||
|
[TestFixture]
|
||||||
|
public class DeleteEnvelopeCommandTests : TestBase
|
||||||
|
{
|
||||||
|
protected override void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task DeleteEnvelope_DraftEnvelope_ShouldSetDeleted_CreateHistory_AndQueueDeletionEmail()
|
||||||
|
{
|
||||||
|
CancellationToken cancel = default;
|
||||||
|
var reason = "Invalid receiver data";
|
||||||
|
|
||||||
|
var envelope = this.CreateEnvelope(User.Id);
|
||||||
|
envelope.Status = EnvelopeStatus.EnvelopeCreated;
|
||||||
|
envelope = await GetRepository<Envelope>().CreateAsync(envelope, cancel);
|
||||||
|
|
||||||
|
var createReceiverCmd = this.CreateReceiverCommand();
|
||||||
|
(var receiver, _) = await Mediator.Send(createReceiverCmd, cancel);
|
||||||
|
|
||||||
|
var envReceiver = this.CreateEnvelopeReceiver(envelope.Id, receiver.Id);
|
||||||
|
await GetRepository<EnvelopeReceiver>().CreateAsync(envReceiver, cancel);
|
||||||
|
|
||||||
|
var result = await Mediator.Send(new DeleteEnvelopeCommand
|
||||||
|
{
|
||||||
|
EnvelopeId = envelope.Id,
|
||||||
|
Reason = reason
|
||||||
|
}.WithAuth(User.Id), cancel);
|
||||||
|
|
||||||
|
Assert.That(result.EnvelopeId, Is.EqualTo(envelope.Id));
|
||||||
|
Assert.That(result.AppliedStatus, Is.EqualTo(EnvelopeStatus.EnvelopeDeleted));
|
||||||
|
Assert.That(result.IsNoOp, Is.False);
|
||||||
|
|
||||||
|
var histories = await GetRepository<History>().Query
|
||||||
|
.Where(h => h.EnvelopeId == envelope.Id && h.Status == EnvelopeStatus.EnvelopeDeleted)
|
||||||
|
.ToListAsync(cancel);
|
||||||
|
Assert.That(histories, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(histories[0].Comment, Is.EqualTo(reason));
|
||||||
|
|
||||||
|
var emails = await GetRepository<EmailOut>().Query
|
||||||
|
.Where(e => e.WfId == (int)EnvelopeStatus.MessageDeletionSent && e.ReferenceId == receiver.Id)
|
||||||
|
.ToListAsync(cancel);
|
||||||
|
Assert.That(emails, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(emails[0].EmailAddress, Is.EqualTo(receiver.EmailAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task DeleteEnvelope_SentEnvelope_ShouldSetWithdrawn()
|
||||||
|
{
|
||||||
|
CancellationToken cancel = default;
|
||||||
|
|
||||||
|
var envelope = this.CreateEnvelope(User.Id);
|
||||||
|
envelope.Status = EnvelopeStatus.EnvelopeQueued;
|
||||||
|
envelope = await GetRepository<Envelope>().CreateAsync(envelope, cancel);
|
||||||
|
|
||||||
|
var result = await Mediator.Send(new DeleteEnvelopeCommand
|
||||||
|
{
|
||||||
|
EnvelopeId = envelope.Id,
|
||||||
|
Reason = "Sender requested cancellation"
|
||||||
|
}.WithAuth(User.Id), cancel);
|
||||||
|
|
||||||
|
Assert.That(result.AppliedStatus, Is.EqualTo(EnvelopeStatus.EnvelopeWithdrawn));
|
||||||
|
|
||||||
|
var histories = await GetRepository<History>().Query
|
||||||
|
.Where(h => h.EnvelopeId == envelope.Id && h.Status == EnvelopeStatus.EnvelopeWithdrawn)
|
||||||
|
.ToListAsync(cancel);
|
||||||
|
Assert.That(histories, Has.Count.EqualTo(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void DeleteEnvelope_MissingReason_ShouldThrowBadRequest()
|
||||||
|
{
|
||||||
|
Assert.ThrowsAsync<BadRequestException>(async () =>
|
||||||
|
await Mediator.Send(new DeleteEnvelopeCommand
|
||||||
|
{
|
||||||
|
EnvelopeId = 123,
|
||||||
|
Reason = " "
|
||||||
|
}.WithAuth(User.Id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task DeleteEnvelope_WrongOwner_ShouldThrowForbidden()
|
||||||
|
{
|
||||||
|
CancellationToken cancel = default;
|
||||||
|
|
||||||
|
var envelope = this.CreateEnvelope(User.Id);
|
||||||
|
envelope = await GetRepository<Envelope>().CreateAsync(envelope, cancel);
|
||||||
|
|
||||||
|
var otherUserCmd = this.CreateUserCommand();
|
||||||
|
var otherUser = await GetRepository<DigitalData.UserManager.Domain.Entities.User>().CreateAsync(otherUserCmd, cancel);
|
||||||
|
|
||||||
|
Assert.ThrowsAsync<ForbiddenException>(async () =>
|
||||||
|
await Mediator.Send(new DeleteEnvelopeCommand
|
||||||
|
{
|
||||||
|
EnvelopeId = envelope.Id,
|
||||||
|
Reason = "Not allowed"
|
||||||
|
}.WithAuth(otherUser.Id), cancel));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,7 +52,7 @@ public class Fake
|
|||||||
|
|
||||||
services.AddEnvelopeGeneratorInfrastructureServices(opt =>
|
services.AddEnvelopeGeneratorInfrastructureServices(opt =>
|
||||||
{
|
{
|
||||||
opt.AddDbContext(dbCtxOpt => dbCtxOpt.UseInMemoryDatabase("EnvelopeGeneratorTestDb"));
|
opt.AddDbContext(dbCtxOpt => dbCtxOpt.UseInMemoryDatabase($"EnvelopeGeneratorTestDb_{Guid.NewGuid():N}"));
|
||||||
|
|
||||||
opt.AddDbTriggerParams(context.Configuration);
|
opt.AddDbTriggerParams(context.Configuration);
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,10 @@ public abstract class TestBase : Faker
|
|||||||
|
|
||||||
// Add seed email templates
|
// Add seed email templates
|
||||||
foreach (var temp in SeedEmailTemplates)
|
foreach (var temp in SeedEmailTemplates)
|
||||||
|
{
|
||||||
|
temp.LangCode ??= "de";
|
||||||
await repo.CreateAsync(temp);
|
await repo.CreateAsync(temp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TearDown]
|
[TearDown]
|
||||||
|
|||||||
Reference in New Issue
Block a user