Refactor EnvelopeType handling and improve validation
Removed AutoMapper dependency in `ReadEnvelopeTypesQueryHandler` and replaced it with manual projection to address null-materialization issues. Updated `EnvelopeType` entity properties to nullable types to align with the database schema. Adjusted computed properties to handle null values gracefully. Enhanced `SenderEditorPage` with authentication checks, improved handling of reminder and expiry settings, and added validation for user input. Introduced `NormalizeSettings` and `ValidateSettings` methods to ensure consistent and valid configurations. Refactored checkbox value parsing for better reliability. Injected `AuthService` and `IStringLocalizer` for authentication and localization support. Improved error messaging and localized validation errors. Performed minor code cleanup and refactoring for better readability and maintainability.
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
using EnvelopeGenerator.Application.Common.Dto;
|
using EnvelopeGenerator.Application.Common.Dto;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
using AutoMapper;
|
|
||||||
using DigitalData.Core.Abstraction.Application.Repository;
|
using DigitalData.Core.Abstraction.Application.Repository;
|
||||||
using EnvelopeGenerator.Domain.Entities;
|
using EnvelopeGenerator.Domain.Entities;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -18,17 +17,14 @@ public record ReadEnvelopeTypesQuery : IRequest<IEnumerable<EnvelopeTypeDto>>;
|
|||||||
public class ReadEnvelopeTypesQueryHandler : IRequestHandler<ReadEnvelopeTypesQuery, IEnumerable<EnvelopeTypeDto>>
|
public class ReadEnvelopeTypesQueryHandler : IRequestHandler<ReadEnvelopeTypesQuery, IEnumerable<EnvelopeTypeDto>>
|
||||||
{
|
{
|
||||||
private readonly IRepository<EnvelopeType> _repository;
|
private readonly IRepository<EnvelopeType> _repository;
|
||||||
private readonly IMapper _mapper;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="repository"></param>
|
/// <param name="repository"></param>
|
||||||
/// <param name="mapper"></param>
|
public ReadEnvelopeTypesQueryHandler(IRepository<EnvelopeType> repository)
|
||||||
public ReadEnvelopeTypesQueryHandler(IRepository<EnvelopeType> repository, IMapper mapper)
|
|
||||||
{
|
{
|
||||||
_repository = repository;
|
_repository = repository;
|
||||||
_mapper = mapper;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -39,7 +35,28 @@ public class ReadEnvelopeTypesQueryHandler : IRequestHandler<ReadEnvelopeTypesQu
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<IEnumerable<EnvelopeTypeDto>> Handle(ReadEnvelopeTypesQuery request, CancellationToken cancellationToken)
|
public async Task<IEnumerable<EnvelopeTypeDto>> Handle(ReadEnvelopeTypesQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var types = await _repository.Query.ToListAsync(cancellationToken);
|
// TODO: Temporary manual projection to avoid null-materialization crashes from legacy nullable DB columns in EnvelopeType.
|
||||||
return _mapper.Map<IEnumerable<EnvelopeTypeDto>>(types);
|
// Revisit and switch to AutoMapper query projection (ProjectTo) after schema nullability and mapping contracts are stabilized.
|
||||||
|
return await _repository.Query
|
||||||
|
.AsNoTracking()
|
||||||
|
.Select(t => new EnvelopeTypeDto
|
||||||
|
{
|
||||||
|
Id = t.Id,
|
||||||
|
Title = t.Title ?? string.Empty,
|
||||||
|
Language = string.IsNullOrWhiteSpace(t.Language) ? "de-DE" : t.Language,
|
||||||
|
ExpiresDays = t.ExpiresDays,
|
||||||
|
CertificationType = (int?)t.CertificationType,
|
||||||
|
UseAccessCode = t.UseAccessCode,
|
||||||
|
FinalEmailToCreator = (int?)t.FinalEmailToCreator,
|
||||||
|
FinalEmailToReceivers = (int?)t.FinalEmailToReceivers,
|
||||||
|
AddedWhen = t.AddedWhen,
|
||||||
|
ChangedWhen = t.ChangedWhen,
|
||||||
|
ExpiresWarningDays = t.ExpiresWarningDays,
|
||||||
|
SendReminderEmails = t.SendReminderEmails,
|
||||||
|
FirstReminderDays = t.FirstReminderDays,
|
||||||
|
ReminderIntervalDays = t.ReminderIntervalDays,
|
||||||
|
ContractType = t.ContractType
|
||||||
|
})
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,41 +22,41 @@ namespace EnvelopeGenerator.Domain.Entities
|
|||||||
public string Language { get; set; }
|
public string Language { get; set; }
|
||||||
|
|
||||||
[Column("EXPIRES_DAYS")]
|
[Column("EXPIRES_DAYS")]
|
||||||
public int ExpiresDays { get; set; }
|
public int? ExpiresDays { get; set; }
|
||||||
|
|
||||||
[Column("CERTIFICATION_TYPE")]
|
[Column("CERTIFICATION_TYPE")]
|
||||||
public Constants.CertificationType CertificationType { get; set; } = Constants.CertificationType.AdvancedElectronicSignature;
|
public Constants.CertificationType? CertificationType { get; set; }
|
||||||
|
|
||||||
[Column("USE_ACCESS_CODE")]
|
[Column("USE_ACCESS_CODE")]
|
||||||
public bool UseAccessCode { get; set; }
|
public bool? UseAccessCode { get; set; }
|
||||||
|
|
||||||
[Column("FINAL_EMAIL_TO_CREATOR")]
|
[Column("FINAL_EMAIL_TO_CREATOR")]
|
||||||
public Constants.FinalEmailType FinalEmailToCreator { get; set; }
|
public Constants.FinalEmailType? FinalEmailToCreator { get; set; }
|
||||||
|
|
||||||
[Column("FINAL_EMAIL_TO_RECEIVERS")]
|
[Column("FINAL_EMAIL_TO_RECEIVERS")]
|
||||||
public Constants.FinalEmailType FinalEmailToReceivers { get; set; }
|
public Constants.FinalEmailType? FinalEmailToReceivers { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[Column("ADDED_WHEN", TypeName = "datetime")]
|
[Column("ADDED_WHEN", TypeName = "datetime")]
|
||||||
public DateTime AddedWhen { get; set; }
|
public DateTime AddedWhen { get; set; }
|
||||||
|
|
||||||
[Column("CHANGED_WHEN", TypeName = "datetime")]
|
[Column("CHANGED_WHEN", TypeName = "datetime")]
|
||||||
public DateTime ChangedWhen { get; set; }
|
public DateTime? ChangedWhen { get; set; }
|
||||||
|
|
||||||
[Column("EXPIRES_WARNING_DAYS")]
|
[Column("EXPIRES_WARNING_DAYS")]
|
||||||
public int ExpiresWarningDays { get; set; }
|
public int? ExpiresWarningDays { get; set; }
|
||||||
|
|
||||||
[Column("SEND_REMINDER_EMAILS")]
|
[Column("SEND_REMINDER_EMAILS")]
|
||||||
public bool SendReminderEmails { get; set; } = false;
|
public bool? SendReminderEmails { get; set; }
|
||||||
|
|
||||||
[Column("FIRST_REMINDER_DAYS")]
|
[Column("FIRST_REMINDER_DAYS")]
|
||||||
public int FirstReminderDays { get; set; } = 0;
|
public int? FirstReminderDays { get; set; }
|
||||||
|
|
||||||
[Column("REMINDER_INTERVAL_DAYS")]
|
[Column("REMINDER_INTERVAL_DAYS")]
|
||||||
public int ReminderIntervalDays { get; set; } = 0;
|
public int? ReminderIntervalDays { get; set; }
|
||||||
|
|
||||||
[Column("CONTRACT_TYPE")]
|
[Column("CONTRACT_TYPE")]
|
||||||
public int ContractType { get; set; }
|
public int? ContractType { get; set; }
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public bool TFA_Enabled { get; set; } = false;
|
public bool TFA_Enabled { get; set; } = false;
|
||||||
@@ -70,14 +70,14 @@ namespace EnvelopeGenerator.Domain.Entities
|
|||||||
[NotMapped]
|
[NotMapped]
|
||||||
public int ExpiresWhenDays
|
public int ExpiresWhenDays
|
||||||
{
|
{
|
||||||
get => ExpiresDays;
|
get => ExpiresDays ?? 0;
|
||||||
set => ExpiresDays = value;
|
set => ExpiresDays = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public int ExpiresWarningWhenDays
|
public int ExpiresWarningWhenDays
|
||||||
{
|
{
|
||||||
get => ExpiresWarningDays;
|
get => ExpiresWarningDays ?? 0;
|
||||||
set => ExpiresWarningDays = value;
|
set => ExpiresWarningDays = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
@inject ReceiverService ReceiverService
|
@inject ReceiverService ReceiverService
|
||||||
@inject EnvelopeService EnvelopeService
|
@inject EnvelopeService EnvelopeService
|
||||||
@inject EnvelopeTypeService EnvelopeTypeService
|
@inject EnvelopeTypeService EnvelopeTypeService
|
||||||
|
@inject AuthService AuthService
|
||||||
@inject IStringLocalizer<Resource> UiLocalizer
|
@inject IStringLocalizer<Resource> UiLocalizer
|
||||||
|
|
||||||
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
||||||
@@ -696,6 +697,13 @@
|
|||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
|
var hasAccess = await AuthService.CheckSenderAccessAsync();
|
||||||
|
if (!hasAccess)
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo("/sender/login");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_envelopeTypes = (await EnvelopeTypeService.GetAsync()).ToList();
|
_envelopeTypes = (await EnvelopeTypeService.GetAsync()).ToList();
|
||||||
@@ -768,12 +776,14 @@
|
|||||||
_finalEmailToCreator = envelope.FinalEmailToCreator ?? (int)FinalEmailType.No;
|
_finalEmailToCreator = envelope.FinalEmailToCreator ?? (int)FinalEmailType.No;
|
||||||
_finalEmailToReceivers = envelope.FinalEmailToReceivers ?? (int)FinalEmailType.No;
|
_finalEmailToReceivers = envelope.FinalEmailToReceivers ?? (int)FinalEmailType.No;
|
||||||
|
|
||||||
_sendReminderEmails = envelope.EnvelopeType?.SendReminderEmails ?? false;
|
_sendReminderEmails = envelope.SendReminderEmails ?? envelope.EnvelopeType?.SendReminderEmails ?? false;
|
||||||
_firstReminderDays = envelope.EnvelopeType?.FirstReminderDays ?? 0;
|
_firstReminderDays = envelope.FirstReminderDays ?? envelope.EnvelopeType?.FirstReminderDays ?? 0;
|
||||||
_reminderIntervalDays = envelope.EnvelopeType?.ReminderIntervalDays ?? 0;
|
_reminderIntervalDays = envelope.ReminderIntervalDays ?? envelope.EnvelopeType?.ReminderIntervalDays ?? 0;
|
||||||
_expiresWhenDays = envelope.ExpiresWhenDays ?? envelope.EnvelopeType?.ExpiresDays ?? 0;
|
_expiresWhenDays = envelope.ExpiresWhenDays ?? envelope.EnvelopeType?.ExpiresDays ?? 0;
|
||||||
_expiresWarningWhenDays = envelope.ExpiresWarningWhenDays ?? envelope.EnvelopeType?.ExpiresWarningDays ?? 0;
|
_expiresWarningWhenDays = envelope.ExpiresWarningWhenDays ?? envelope.EnvelopeType?.ExpiresWarningDays ?? 0;
|
||||||
|
|
||||||
|
NormalizeSettings();
|
||||||
|
|
||||||
var envelopeReceivers = envelope.EnvelopeReceivers?.ToList() ?? [];
|
var envelopeReceivers = envelope.EnvelopeReceivers?.ToList() ?? [];
|
||||||
_receivers = envelopeReceivers
|
_receivers = envelopeReceivers
|
||||||
.Select((er, index) => new ReceiverDraft(
|
.Select((er, index) => new ReceiverDraft(
|
||||||
@@ -962,7 +972,7 @@
|
|||||||
|
|
||||||
void OnTfaChanged(ChangeEventArgs e)
|
void OnTfaChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_tfaEnabled = e.Value as bool? ?? false;
|
_tfaEnabled = ParseCheckboxValue(e);
|
||||||
if (_tfaEnabled)
|
if (_tfaEnabled)
|
||||||
_useAccessCode = true;
|
_useAccessCode = true;
|
||||||
}
|
}
|
||||||
@@ -975,12 +985,20 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_useAccessCode = e.Value as bool? ?? false;
|
_useAccessCode = ParseCheckboxValue(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnSendReminderEmailsChanged(ChangeEventArgs e)
|
void OnSendReminderEmailsChanged(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_sendReminderEmails = e.Value as bool? ?? false;
|
_sendReminderEmails = ParseCheckboxValue(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool ParseCheckboxValue(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Value is bool b)
|
||||||
|
return b;
|
||||||
|
|
||||||
|
return bool.TryParse(e.Value?.ToString(), out var parsed) && parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── PDF area click → place field ──
|
// ── PDF area click → place field ──
|
||||||
@@ -1100,6 +1118,13 @@
|
|||||||
|
|
||||||
async Task SaveDraftAsync()
|
async Task SaveDraftAsync()
|
||||||
{
|
{
|
||||||
|
if (!ValidateSettings(out var settingsError))
|
||||||
|
{
|
||||||
|
_saveErrorMessage = settingsError;
|
||||||
|
_savePopupVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_isSaving = true;
|
_isSaving = true;
|
||||||
_saveErrorMessage = null;
|
_saveErrorMessage = null;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
@@ -1140,6 +1165,13 @@
|
|||||||
// ── Send envelope ──
|
// ── Send envelope ──
|
||||||
async Task SendAsync()
|
async Task SendAsync()
|
||||||
{
|
{
|
||||||
|
if (!ValidateSettings(out var settingsError))
|
||||||
|
{
|
||||||
|
_saveErrorMessage = settingsError;
|
||||||
|
_savePopupVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Validation ──
|
// ── Validation ──
|
||||||
_titleTouched = true;
|
_titleTouched = true;
|
||||||
if (!_pdfLoaded || _originalPdfBytes is null)
|
if (!_pdfLoaded || _originalPdfBytes is null)
|
||||||
@@ -1227,6 +1259,8 @@
|
|||||||
|
|
||||||
CreateEnvelopeCommand BuildCreateEnvelopeCommand(bool send)
|
CreateEnvelopeCommand BuildCreateEnvelopeCommand(bool send)
|
||||||
{
|
{
|
||||||
|
NormalizeSettings();
|
||||||
|
|
||||||
var envelopeId = _editingEnvelopeId
|
var envelopeId = _editingEnvelopeId
|
||||||
?? EnvelopeId
|
?? EnvelopeId
|
||||||
?? _loadedEnvelopeId
|
?? _loadedEnvelopeId
|
||||||
@@ -1279,6 +1313,48 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NormalizeSettings()
|
||||||
|
{
|
||||||
|
_language = string.IsNullOrWhiteSpace(_language) ? "de-DE" : _language.Trim();
|
||||||
|
|
||||||
|
if (_tfaEnabled)
|
||||||
|
_useAccessCode = true;
|
||||||
|
|
||||||
|
_firstReminderDays = Math.Max(0, _firstReminderDays);
|
||||||
|
_reminderIntervalDays = Math.Max(0, _reminderIntervalDays);
|
||||||
|
_expiresWhenDays = Math.Max(0, _expiresWhenDays);
|
||||||
|
_expiresWarningWhenDays = Math.Max(0, _expiresWarningWhenDays);
|
||||||
|
|
||||||
|
if (_expiresWhenDays > 0 && _expiresWarningWhenDays > _expiresWhenDays)
|
||||||
|
_expiresWarningWhenDays = _expiresWhenDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ValidateSettings(out string? message)
|
||||||
|
{
|
||||||
|
NormalizeSettings();
|
||||||
|
|
||||||
|
if (_sendReminderEmails && _reminderIntervalDays <= 0)
|
||||||
|
{
|
||||||
|
message = L("SenderEditor_Validation_ReminderInterval", "Bitte geben Sie ein Erinnerungsintervall groesser als 0 Tage ein.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sendReminderEmails && _firstReminderDays < 0)
|
||||||
|
{
|
||||||
|
message = L("SenderEditor_Validation_FirstReminder", "Bitte geben Sie gueltige Tage fuer die erste Erinnerung ein.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_expiresWhenDays > 0 && _expiresWarningWhenDays > _expiresWhenDays)
|
||||||
|
{
|
||||||
|
message = L("SenderEditor_Validation_ExpiryWarning", "Die Ablaufwarnung darf nicht nach dem Ablaufdatum liegen.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
message = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int? GetEnvelopeIdFromCurrentUri()
|
int? GetEnvelopeIdFromCurrentUri()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
Reference in New Issue
Block a user