Compare commits
73 Commits
feat/blazo
...
bugfix/rea
| Author | SHA1 | Date | |
|---|---|---|---|
| 7cb1546934 | |||
| 60db762bcc | |||
| 5e840db04c | |||
| e724a74f9c | |||
| 48b7afcdc1 | |||
| 717da90c01 | |||
| 8054bb377d | |||
| 200258ff73 | |||
| fa73d939b5 | |||
| ca9e25abcb | |||
| 82831991b0 | |||
|
|
260e8d53ba | ||
|
|
0fd174ee0c | ||
|
|
ab4cd7c254 | ||
| 1f5468b1ac | |||
| b20aafe7a5 | |||
| 466d0a3a7a | |||
| 7281cb47c3 | |||
| eb5db3d6be | |||
| 8a534b84d0 | |||
| c523153654 | |||
| 82c85643c8 | |||
| 69892d566c | |||
| 2f41348c59 | |||
| 0d56ac7448 | |||
| 18a563ecd1 | |||
| 73df248d15 | |||
| 7c7674c822 | |||
| 65f606f573 | |||
| 0341505f8d | |||
| d4eee1718e | |||
| 9b042d8f45 | |||
|
|
ad0c847172 | ||
| f6d57b1e38 | |||
| b64d2b7478 | |||
|
|
f8c7f60cf9 | ||
| 44edef8ba1 | |||
| 647c5d2353 | |||
| 4ce1d2a370 | |||
| bcc53bf9f1 | |||
| f1e38e3bd3 | |||
| e095860b17 | |||
| 9cfc74aa88 | |||
| 7cd6ca3a5f | |||
| 9b660cb25a | |||
| 3d43d1896d | |||
| bcc17f6def | |||
| 8e3c334fa3 | |||
| 08299451bb | |||
| 59d6d25bdd | |||
| 9a516ab3c9 | |||
| 8fed342dc5 | |||
| f44643aa3e | |||
| 86c99596c4 | |||
| 2d0c08b2ce | |||
| e0aa963184 | |||
| afa3694cd7 | |||
| 48f4ea0c50 | |||
| 74c4ddda83 | |||
| f9b1e583df | |||
| 7c8e0d8481 | |||
| 6d2ec4cc0b | |||
| 898097cdb5 | |||
| 689a1b355a | |||
| 3b3330bd54 | |||
| 511fad3950 | |||
| f5f137396e | |||
| 0d78e9b8f5 | |||
| 8258d9f43f | |||
| 01f3335238 | |||
| 1d0c758e00 | |||
| 711f7d12e8 | |||
| 43d89699a9 |
@@ -1,406 +0,0 @@
|
|||||||
using EnvelopeGenerator.API.Models;
|
|
||||||
using EnvelopeGenerator.Application.Common.Extensions;
|
|
||||||
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
|
||||||
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
|
||||||
using EnvelopeGenerator.Domain.Constants;
|
|
||||||
using EnvelopeGenerator.API.Extensions;
|
|
||||||
using MediatR;
|
|
||||||
using Microsoft.AspNetCore.Authentication;
|
|
||||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using OtpNet;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.API.Controllers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// REST-API für den Empfänger-Authentifizierungs-Flow.
|
|
||||||
///
|
|
||||||
/// Entspricht der Logik in EnvelopeGenerator.Web.Controllers.EnvelopeController
|
|
||||||
/// (Main + LogInEnvelope), aber gibt JSON statt Views zurück.
|
|
||||||
///
|
|
||||||
/// Der Blazor-Client (ReceiverUI) ruft diese Endpunkte auf.
|
|
||||||
///
|
|
||||||
/// FLOW:
|
|
||||||
/// 1. Client ruft GET /api/receiverauth/{key}/status → Prüft Status
|
|
||||||
/// 2. Client ruft POST /api/receiverauth/{key}/access-code → Sendet AccessCode
|
|
||||||
/// 3. Client ruft POST /api/receiverauth/{key}/tfa → Sendet TFA-Code
|
|
||||||
///
|
|
||||||
/// Nach erfolgreicher Authentifizierung wird ein Cookie gesetzt (SignInEnvelopeAsync).
|
|
||||||
/// Danach kann der Client die Dokument-Daten über die bestehenden Envelope-Endpunkte laden.
|
|
||||||
/// </summary>
|
|
||||||
[Route("api/[controller]")]
|
|
||||||
[ApiController]
|
|
||||||
public class ReceiverAuthController : ControllerBase
|
|
||||||
{
|
|
||||||
private readonly ILogger<ReceiverAuthController> _logger;
|
|
||||||
private readonly IMediator _mediator;
|
|
||||||
private readonly IEnvelopeReceiverService _envRcvService;
|
|
||||||
private readonly IEnvelopeHistoryService _historyService;
|
|
||||||
private readonly IAuthenticator _authenticator;
|
|
||||||
private readonly IReceiverService _rcvService;
|
|
||||||
private readonly IEnvelopeSmsHandler _envSmsHandler;
|
|
||||||
|
|
||||||
public ReceiverAuthController(
|
|
||||||
ILogger<ReceiverAuthController> logger,
|
|
||||||
IMediator mediator,
|
|
||||||
IEnvelopeReceiverService envRcvService,
|
|
||||||
IEnvelopeHistoryService historyService,
|
|
||||||
IAuthenticator authenticator,
|
|
||||||
IReceiverService rcvService,
|
|
||||||
IEnvelopeSmsHandler envSmsHandler)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_mediator = mediator;
|
|
||||||
_envRcvService = envRcvService;
|
|
||||||
_historyService = historyService;
|
|
||||||
_authenticator = authenticator;
|
|
||||||
_rcvService = rcvService;
|
|
||||||
_envSmsHandler = envSmsHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
// ENDPUNKT 1: STATUS PRÜFEN
|
|
||||||
// Entspricht: Web.EnvelopeController.Main()
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Prüft den aktuellen Status eines Umschlags für den Empfänger.
|
|
||||||
/// Entscheidet ob: NotFound, Rejected, Signed, AccessCode nötig, oder direkt anzeigen.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">Der EnvelopeReceiver-Key aus der URL (Base64-kodiert)</param>
|
|
||||||
/// <param name="cancel">Cancellation-Token</param>
|
|
||||||
/// <returns>ReceiverAuthResponse mit dem aktuellen Status</returns>
|
|
||||||
[HttpGet("{key}/status")]
|
|
||||||
public async Task<IActionResult> GetStatus([FromRoute] string key, CancellationToken cancel)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// ── Key dekodieren ──
|
|
||||||
if (!key.TryDecode(out var decoded))
|
|
||||||
return NotFound(new ReceiverAuthResponse { Status = "not_found" });
|
|
||||||
|
|
||||||
// ── ReadOnly-Links ──
|
|
||||||
if (decoded.GetEncodeType() == EncodeType.EnvelopeReceiverReadOnly)
|
|
||||||
{
|
|
||||||
return Ok(new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "show_document",
|
|
||||||
ReadOnly = true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── EnvelopeReceiver laden ──
|
|
||||||
var er = await _mediator.ReadEnvelopeReceiverAsync(key, cancel);
|
|
||||||
if (er is null)
|
|
||||||
return NotFound(new ReceiverAuthResponse { Status = "not_found" });
|
|
||||||
|
|
||||||
// ── Abgelehnt? ──
|
|
||||||
var rejRcvrs = await _historyService.ReadRejectingReceivers(er.Envelope!.Id);
|
|
||||||
if (rejRcvrs.Any())
|
|
||||||
{
|
|
||||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
|
||||||
return Ok(new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "rejected",
|
|
||||||
Title = er.Envelope.Title,
|
|
||||||
SenderEmail = er.Envelope.User?.Email
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Bereits signiert? ──
|
|
||||||
if (await _historyService.IsSigned(
|
|
||||||
envelopeId: er.Envelope.Id,
|
|
||||||
userReference: er.Receiver!.EmailAddress))
|
|
||||||
{
|
|
||||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
|
||||||
return Ok(new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "already_signed",
|
|
||||||
Title = er.Envelope.Title,
|
|
||||||
SenderEmail = er.Envelope.User?.Email
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Kein AccessCode nötig? → Direkt SignIn ──
|
|
||||||
if (!er.Envelope.UseAccessCode)
|
|
||||||
{
|
|
||||||
(string? uuid, string? signature) = decoded.ParseEnvelopeReceiverId();
|
|
||||||
var erSecretRes = await _envRcvService.ReadWithSecretByUuidSignatureAsync(
|
|
||||||
uuid: uuid!, signature: signature!);
|
|
||||||
|
|
||||||
if (erSecretRes.IsFailed)
|
|
||||||
return NotFound(new ReceiverAuthResponse { Status = "not_found" });
|
|
||||||
|
|
||||||
await HttpContext.SignInEnvelopeAsync(erSecretRes.Data, Role.ReceiverFull);
|
|
||||||
|
|
||||||
return Ok(new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "show_document",
|
|
||||||
Title = er.Envelope.Title,
|
|
||||||
Message = er.Envelope.Message,
|
|
||||||
SenderEmail = er.Envelope.User?.Email,
|
|
||||||
ReadOnly = er.Envelope.ReadOnly
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── AccessCode nötig ──
|
|
||||||
// HINWEIS: Die E-Mail mit dem AccessCode wird NICHT hier gesendet.
|
|
||||||
// Das passiert bereits im Web-Projekt, wenn der Link generiert wird.
|
|
||||||
// Der Blazor-Flow übernimmt erst NACH dem E-Mail-Versand.
|
|
||||||
bool accessCodeAlreadyRequested = await _historyService.AccessCodeAlreadyRequested(
|
|
||||||
envelopeId: er.Envelope.Id,
|
|
||||||
userReference: er.Receiver.EmailAddress);
|
|
||||||
|
|
||||||
if (!accessCodeAlreadyRequested)
|
|
||||||
{
|
|
||||||
// AccessCode wurde noch nie angefordert — das bedeutet der Empfänger
|
|
||||||
// kommt zum ersten Mal. Wir zeichnen es auf, aber die E-Mail
|
|
||||||
// wurde bereits vom Web-Projekt gesendet.
|
|
||||||
await _historyService.RecordAsync(
|
|
||||||
er.EnvelopeId, er.Receiver.EmailAddress, EnvelopeStatus.AccessCodeRequested);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Prüfe ob der Nutzer bereits eingeloggt ist ──
|
|
||||||
if (User.IsInRole(Role.ReceiverFull))
|
|
||||||
{
|
|
||||||
return Ok(new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "show_document",
|
|
||||||
Title = er.Envelope.Title,
|
|
||||||
Message = er.Envelope.Message,
|
|
||||||
SenderEmail = er.Envelope.User?.Email,
|
|
||||||
ReadOnly = er.Envelope.ReadOnly
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "requires_access_code",
|
|
||||||
Title = er.Envelope.Title,
|
|
||||||
SenderEmail = er.Envelope.User?.Email,
|
|
||||||
TfaEnabled = er.Envelope.TFAEnabled,
|
|
||||||
HasPhoneNumber = er.HasPhoneNumber
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error checking status for key {Key}", key);
|
|
||||||
return StatusCode(500, new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "error",
|
|
||||||
ErrorMessage = "Ein unerwarteter Fehler ist aufgetreten."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
// ENDPUNKT 2: ACCESS-CODE PRÜFEN
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Prüft den eingegebenen Zugangscode.
|
|
||||||
/// Bei Erfolg: SignIn oder TFA-Weiterleitung.
|
|
||||||
/// Bei Fehler: Fehlermeldung zurückgeben.
|
|
||||||
/// </summary>
|
|
||||||
[HttpPost("{key}/access-code")]
|
|
||||||
public async Task<IActionResult> SubmitAccessCode(
|
|
||||||
[FromRoute] string key,
|
|
||||||
[FromBody] AccessCodeRequest request,
|
|
||||||
CancellationToken cancel)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// ── Key dekodieren + Daten laden ──
|
|
||||||
(string? uuid, string? signature) = key.DecodeEnvelopeReceiverId();
|
|
||||||
if (uuid is null || signature is null)
|
|
||||||
return NotFound(new ReceiverAuthResponse { Status = "not_found" });
|
|
||||||
|
|
||||||
var erSecretRes = await _envRcvService.ReadWithSecretByUuidSignatureAsync(
|
|
||||||
uuid: uuid, signature: signature);
|
|
||||||
|
|
||||||
if (erSecretRes.IsFailed)
|
|
||||||
return NotFound(new ReceiverAuthResponse { Status = "not_found" });
|
|
||||||
|
|
||||||
var erSecret = erSecretRes.Data;
|
|
||||||
|
|
||||||
// ── AccessCode prüfen ──
|
|
||||||
if (erSecret.AccessCode != request.AccessCode)
|
|
||||||
{
|
|
||||||
await _historyService.RecordAsync(
|
|
||||||
erSecret.EnvelopeId,
|
|
||||||
erSecret.Receiver!.EmailAddress,
|
|
||||||
EnvelopeStatus.AccessCodeIncorrect);
|
|
||||||
|
|
||||||
return Unauthorized(new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "requires_access_code",
|
|
||||||
Title = erSecret.Envelope!.Title,
|
|
||||||
SenderEmail = erSecret.Envelope.User?.Email,
|
|
||||||
TfaEnabled = erSecret.Envelope.TFAEnabled,
|
|
||||||
HasPhoneNumber = erSecret.HasPhoneNumber,
|
|
||||||
ErrorMessage = "Falscher Zugangscode."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── AccessCode korrekt ──
|
|
||||||
await _historyService.RecordAsync(
|
|
||||||
erSecret.EnvelopeId,
|
|
||||||
erSecret.Receiver!.EmailAddress,
|
|
||||||
EnvelopeStatus.AccessCodeCorrect);
|
|
||||||
|
|
||||||
// ── TFA erforderlich? ──
|
|
||||||
if (erSecret.Envelope!.TFAEnabled)
|
|
||||||
{
|
|
||||||
var rcv = erSecret.Receiver;
|
|
||||||
if (rcv.TotpSecretkey is null)
|
|
||||||
{
|
|
||||||
rcv.TotpSecretkey = _authenticator.GenerateTotpSecretKey();
|
|
||||||
await _rcvService.UpdateAsync(rcv);
|
|
||||||
}
|
|
||||||
|
|
||||||
await HttpContext.SignInEnvelopeAsync(erSecret, Role.ReceiverTFA);
|
|
||||||
|
|
||||||
if (request.PreferSms)
|
|
||||||
{
|
|
||||||
var (smsRes, expiration) = await _envSmsHandler.SendTotpAsync(erSecret);
|
|
||||||
|
|
||||||
return Ok(new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "requires_tfa",
|
|
||||||
TfaType = "sms",
|
|
||||||
TfaExpiration = expiration,
|
|
||||||
Title = erSecret.Envelope.Title,
|
|
||||||
SenderEmail = erSecret.Envelope.User?.Email,
|
|
||||||
HasPhoneNumber = erSecret.HasPhoneNumber
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Ok(new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "requires_tfa",
|
|
||||||
TfaType = "authenticator",
|
|
||||||
Title = erSecret.Envelope.Title,
|
|
||||||
SenderEmail = erSecret.Envelope.User?.Email,
|
|
||||||
HasPhoneNumber = erSecret.HasPhoneNumber
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Kein TFA → Direkt SignIn ──
|
|
||||||
await HttpContext.SignInEnvelopeAsync(erSecret, Role.ReceiverFull);
|
|
||||||
|
|
||||||
return Ok(new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "show_document",
|
|
||||||
Title = erSecret.Envelope.Title,
|
|
||||||
Message = erSecret.Envelope.Message,
|
|
||||||
SenderEmail = erSecret.Envelope.User?.Email,
|
|
||||||
ReadOnly = erSecret.Envelope.ReadOnly
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error submitting access code for key {Key}", key);
|
|
||||||
return StatusCode(500, new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "error",
|
|
||||||
ErrorMessage = "Ein unerwarteter Fehler ist aufgetreten."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
// ENDPUNKT 3: TFA-CODE PRÜFEN
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Prüft den TFA-Code (SMS oder Authenticator).
|
|
||||||
/// Setzt voraus, dass der Nutzer bereits mit ReceiverTFA-Rolle eingeloggt ist.
|
|
||||||
/// </summary>
|
|
||||||
[HttpPost("{key}/tfa")]
|
|
||||||
public async Task<IActionResult> SubmitTfaCode(
|
|
||||||
[FromRoute] string key,
|
|
||||||
[FromBody] TfaCodeRequest request,
|
|
||||||
CancellationToken cancel)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!User.IsInRole(Role.ReceiverTFA))
|
|
||||||
return Unauthorized(new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "requires_access_code",
|
|
||||||
ErrorMessage = "Bitte zuerst den Zugangscode eingeben."
|
|
||||||
});
|
|
||||||
|
|
||||||
(string? uuid, string? signature) = key.DecodeEnvelopeReceiverId();
|
|
||||||
if (uuid is null || signature is null)
|
|
||||||
return NotFound(new ReceiverAuthResponse { Status = "not_found" });
|
|
||||||
|
|
||||||
var erSecretRes = await _envRcvService.ReadWithSecretByUuidSignatureAsync(
|
|
||||||
uuid: uuid, signature: signature);
|
|
||||||
|
|
||||||
if (erSecretRes.IsFailed)
|
|
||||||
return NotFound(new ReceiverAuthResponse { Status = "not_found" });
|
|
||||||
|
|
||||||
var erSecret = erSecretRes.Data;
|
|
||||||
|
|
||||||
if (erSecret.Receiver!.TotpSecretkey is null)
|
|
||||||
{
|
|
||||||
_logger.LogError("TotpSecretkey is null for receiver {Signature}", signature);
|
|
||||||
return StatusCode(500, new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "error",
|
|
||||||
ErrorMessage = "TFA-Konfiguration fehlt."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool codeValid;
|
|
||||||
|
|
||||||
if (request.Type == "sms")
|
|
||||||
{
|
|
||||||
codeValid = _envSmsHandler.VerifyTotp(request.Code, erSecret.Receiver.TotpSecretkey);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
codeValid = _authenticator.VerifyTotp(
|
|
||||||
request.Code,
|
|
||||||
erSecret.Receiver.TotpSecretkey,
|
|
||||||
window: VerificationWindow.RfcSpecifiedNetworkDelay);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!codeValid)
|
|
||||||
{
|
|
||||||
return Unauthorized(new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "requires_tfa",
|
|
||||||
TfaType = request.Type,
|
|
||||||
Title = erSecret.Envelope!.Title,
|
|
||||||
SenderEmail = erSecret.Envelope.User?.Email,
|
|
||||||
HasPhoneNumber = erSecret.HasPhoneNumber,
|
|
||||||
ErrorMessage = "Falscher Code."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await HttpContext.SignInEnvelopeAsync(erSecret, Role.ReceiverFull);
|
|
||||||
|
|
||||||
return Ok(new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "show_document",
|
|
||||||
Title = erSecret.Envelope!.Title,
|
|
||||||
Message = erSecret.Envelope.Message,
|
|
||||||
SenderEmail = erSecret.Envelope.User?.Email,
|
|
||||||
ReadOnly = erSecret.Envelope.ReadOnly
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error submitting TFA code for key {Key}", key);
|
|
||||||
return StatusCode(500, new ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
Status = "error",
|
|
||||||
ErrorMessage = "Ein unerwarteter Fehler ist aufgetreten."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.API.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Einheitliche Antwort des ReceiverAuthControllers.
|
|
||||||
///
|
|
||||||
/// WARUM ein einziges Response-Objekt für alle Endpunkte?
|
|
||||||
/// - Der Client braucht nur ein Format zu verstehen
|
|
||||||
/// - Der Status-String bestimmt, welche Felder relevant sind
|
|
||||||
/// - Entspricht dem, was der Web-Controller bisher über ViewData verteilt hat
|
|
||||||
///
|
|
||||||
/// Status-Werte und was sie bedeuten:
|
|
||||||
/// - "requires_access_code" → AccessCode-Eingabe zeigen
|
|
||||||
/// - "requires_tfa" → TFA-Code-Eingabe zeigen (nach AccessCode)
|
|
||||||
/// - "show_document" → Dokument laden und anzeigen
|
|
||||||
/// - "already_signed" → Info-Seite "Bereits unterschrieben"
|
|
||||||
/// - "rejected" → Info-Seite "Abgelehnt"
|
|
||||||
/// - "not_found" → Fehler-Seite "Nicht gefunden"
|
|
||||||
/// - "expired" → Fehler-Seite "Link abgelaufen"
|
|
||||||
/// </summary>
|
|
||||||
public class ReceiverAuthResponse
|
|
||||||
{
|
|
||||||
/// <summary>Aktueller Status des Empfänger-Flows</summary>
|
|
||||||
public required string Status { get; init; }
|
|
||||||
|
|
||||||
/// <summary>Titel des Umschlags (z.B. "Vertragsdokument")</summary>
|
|
||||||
public string? Title { get; init; }
|
|
||||||
|
|
||||||
/// <summary>Nachricht des Absenders</summary>
|
|
||||||
public string? Message { get; init; }
|
|
||||||
|
|
||||||
/// <summary>E-Mail des Absenders (für Rückfragen-Hinweis)</summary>
|
|
||||||
public string? SenderEmail { get; init; }
|
|
||||||
|
|
||||||
/// <summary>Name des Empfängers</summary>
|
|
||||||
public string? ReceiverName { get; init; }
|
|
||||||
|
|
||||||
/// <summary>Ob TFA für diesen Umschlag aktiviert ist</summary>
|
|
||||||
public bool TfaEnabled { get; init; }
|
|
||||||
|
|
||||||
/// <summary>Ob der Empfänger eine Telefonnummer hat (für SMS-TFA)</summary>
|
|
||||||
public bool HasPhoneNumber { get; init; }
|
|
||||||
|
|
||||||
/// <summary>Ob das Dokument nur gelesen werden soll (ReadAndConfirm)</summary>
|
|
||||||
public bool ReadOnly { get; init; }
|
|
||||||
|
|
||||||
/// <summary>TFA-Typ: "sms" oder "authenticator" (wenn Status = "requires_tfa")</summary>
|
|
||||||
public string? TfaType { get; init; }
|
|
||||||
|
|
||||||
/// <summary>Ablaufzeit des SMS-Codes (für Countdown-Timer)</summary>
|
|
||||||
public DateTime? TfaExpiration { get; init; }
|
|
||||||
|
|
||||||
/// <summary>Fehlermeldung (z.B. "Falscher Zugangscode")</summary>
|
|
||||||
public string? ErrorMessage { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Request-Body für POST /api/receiverauth/{key}/access-code
|
|
||||||
/// </summary>
|
|
||||||
public class AccessCodeRequest
|
|
||||||
{
|
|
||||||
/// <summary>Der vom Empfänger eingegebene Zugangscode</summary>
|
|
||||||
public required string AccessCode { get; init; }
|
|
||||||
|
|
||||||
/// <summary>Ob SMS statt Authenticator bevorzugt wird</summary>
|
|
||||||
public bool PreferSms { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Request-Body für POST /api/receiverauth/{key}/tfa
|
|
||||||
/// </summary>
|
|
||||||
public class TfaCodeRequest
|
|
||||||
{
|
|
||||||
/// <summary>Der eingegebene TFA-Code (6-stellig)</summary>
|
|
||||||
public required string Code { get; init; }
|
|
||||||
|
|
||||||
/// <summary>"sms" oder "authenticator"</summary>
|
|
||||||
public required string Type { get; init; }
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
using DigitalData.UserManager.Application.DTOs.User;
|
using DigitalData.UserManager.Application.DTOs.User;
|
||||||
using EnvelopeGenerator.Domain.Constants;
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
using EnvelopeGenerator.Domain.Entities;
|
using EnvelopeGenerator.Domain.Entities;
|
||||||
|
using EnvelopeGenerator.Domain.Interfaces;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Application.Common.Dto;
|
namespace EnvelopeGenerator.Application.Common.Dto;
|
||||||
@@ -10,7 +11,7 @@ namespace EnvelopeGenerator.Application.Common.Dto;
|
|||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ApiExplorerSettings(IgnoreApi = true)]
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
public record EnvelopeDto
|
public record EnvelopeDto : IEnvelope
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
@@ -74,10 +75,12 @@ public record EnvelopeDto
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int? EnvelopeTypeId { get; set; }
|
public int? EnvelopeTypeId { get; set; }
|
||||||
|
|
||||||
|
// TODO: use ReadAndConfirm property name
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ReadOnly => EnvelopeTypeId == 2;
|
[Obsolete("Use EnvelopeExtensions.IsReadAndConfirm extension metot instead.")]
|
||||||
|
public bool ReadOnly => this.IsReadAndConfirm();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -29,15 +29,12 @@ public class DocStatusHandler : INotificationHandler<DocSignedNotification>
|
|||||||
/// <param name="notification"></param>
|
/// <param name="notification"></param>
|
||||||
/// <param name="cancel"></param>
|
/// <param name="cancel"></param>
|
||||||
/// <returns></returns>
|
/// <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()
|
EnvelopeId = notification.EnvelopeId,
|
||||||
{
|
ReceiverId = notification.ReceiverId,
|
||||||
Envelope = new() { Id = notification.EnvelopeId },
|
|
||||||
Receiver = new() { Id = notification.ReceiverId},
|
|
||||||
Value = notification.PsPdfKitAnnotation is PsPdfKitAnnotation annot
|
Value = notification.PsPdfKitAnnotation is PsPdfKitAnnotation annot
|
||||||
? JsonSerializer.Serialize(annot.Instant, Format.Json.ForAnnotations)
|
? JsonSerializer.Serialize(annot.Instant, Format.Json.ForAnnotations)
|
||||||
: BlankAnnotationJson
|
: BlankAnnotationJson
|
||||||
}, cancel);
|
}, cancel);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ using DigitalData.EmailProfilerDispatcher.Abstraction.Entities;
|
|||||||
using EnvelopeGenerator.Application.Common.Configurations;
|
using EnvelopeGenerator.Application.Common.Configurations;
|
||||||
using EnvelopeGenerator.Domain.Entities;
|
using EnvelopeGenerator.Domain.Entities;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using EnvelopeGenerator.Domain.Interfaces;
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Application.Common.Notifications.DocSigned.Handlers;
|
namespace EnvelopeGenerator.Application.Common.Notifications.DocSigned.Handlers;
|
||||||
|
|
||||||
@@ -45,6 +46,25 @@ public class SendSignedMailHandler : SendMailHandler<DocSignedNotification>
|
|||||||
{ "[DOCUMENT_TITLE]", notification.Envelope?.Title ?? string.Empty },
|
{ "[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;
|
return placeHolders;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,12 +8,22 @@ namespace EnvelopeGenerator.Application.DocStatus.Commands;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public record CreateDocStatusCommand : ModifyDocStatusCommandBase, IRequest<DocumentStatus>
|
public record CreateDocStatusCommand : IRequest<DocumentStatus>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets timestamp when this record was added. Returns the StatusChangedWhen value.
|
///
|
||||||
/// </summary>
|
/// </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>
|
/// <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;
|
namespace EnvelopeGenerator.Application.DocStatus.Commands;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public record UpdateDocStatusCommand : ModifyDocStatusCommandBase
|
/// <param name="Value"></param>
|
||||||
|
public record DocStatusUpdateDto(string? Value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public record UpdateDocStatusCommand : UpdateCommand<DocStatusUpdateDto, DocumentStatus>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets timestamp when this record was added. Returns the StatusChangedWhen value.
|
///
|
||||||
/// </summary>
|
/// </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>()
|
CreateMap<CreateDocStatusCommand, DocumentStatus>()
|
||||||
.ForMember(dest => dest.Envelope, opt => opt.Ignore())
|
.ForMember(dest => dest.Envelope, opt => opt.Ignore())
|
||||||
.ForMember(dest => dest.Receiver, 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();
|
.MapAddedWhen();
|
||||||
|
|
||||||
CreateMap<UpdateDocStatusCommand, DocumentStatus>()
|
CreateMap<UpdateDocStatusCommand, DocumentStatus>()
|
||||||
.ForMember(dest => dest.Envelope, opt => opt.Ignore())
|
.ForMember(dest => dest.Envelope, opt => opt.Ignore())
|
||||||
.ForMember(dest => dest.Receiver, opt => opt.Ignore())
|
.ForMember(dest => dest.Receiver, opt => opt.Ignore())
|
||||||
|
.ForMember(dest => dest.StatusChangedWhen, opt => opt.MapFrom(src => DateTime.UtcNow))
|
||||||
.MapChangedWhen();
|
.MapChangedWhen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
<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>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
<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">
|
<PackageReference Include="CommandDotNet">
|
||||||
<Version>7.0.5</Version>
|
<Version>7.0.5</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@@ -88,7 +88,6 @@
|
|||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||||
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
|
|
||||||
<PackageReference Include="CommandDotNet">
|
<PackageReference Include="CommandDotNet">
|
||||||
<Version>8.1.1</Version>
|
<Version>8.1.1</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
@@ -96,7 +95,6 @@
|
|||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||||
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
|
|
||||||
<PackageReference Include="CommandDotNet">
|
<PackageReference Include="CommandDotNet">
|
||||||
<Version>8.1.1</Version>
|
<Version>8.1.1</Version>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public record CreateHistoryCommand : EnvelopeReceiverQueryBase, IRequest<History
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime AddedWhen { get; } = DateTime.Now;
|
public DateTime AddedWhen { get; } = DateTime.UtcNow;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
|
using EnvelopeGenerator.Application.Common.Extensions;
|
||||||
using EnvelopeGenerator.Application.Histories.Commands;
|
using EnvelopeGenerator.Application.Histories.Commands;
|
||||||
using EnvelopeGenerator.Domain.Entities;
|
using EnvelopeGenerator.Domain.Entities;
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ public class MappingProfile: Profile
|
|||||||
CreateMap<CreateHistoryCommand, History>()
|
CreateMap<CreateHistoryCommand, History>()
|
||||||
.ForMember(dest => dest.Envelope, opt => opt.Ignore())
|
.ForMember(dest => dest.Envelope, opt => opt.Ignore())
|
||||||
.ForMember(dest => dest.Sender, 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>
|
/// <returns></returns>
|
||||||
public static string SignDoc(this IStringLocalizer localizer) => localizer[nameof(SignDoc)].Value;
|
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>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -204,6 +211,13 @@ public static class Extensions
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string DocSigned(this IStringLocalizer localizer) => localizer[nameof(DocSigned)].Value;
|
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>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -239,6 +253,13 @@ public static class Extensions
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string SigAgree(this IStringLocalizer localizer) => localizer[nameof(SigAgree)].Value;
|
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>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -267,6 +288,13 @@ public static class Extensions
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string RejectionInfo1(this IStringLocalizer localizer) => localizer[nameof(RejectionInfo1)].Value;
|
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>
|
||||||
///
|
///
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -295,6 +323,13 @@ public static class Extensions
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string SigningProcessTitle(this IStringLocalizer localizer) => localizer[nameof(SigningProcessTitle)].Value;
|
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>
|
||||||
///
|
///
|
||||||
/// </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>
|
<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>
|
||||||
<data name="RejectionInfo2_ext" xml:space="preserve">
|
<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>
|
||||||
<data name="RejectionReasonQ" xml:space="preserve">
|
<data name="RejectionReasonQ" xml:space="preserve">
|
||||||
<value>Bitte geben Sie einen Grund an:</value>
|
<value>Bitte geben Sie einen Grund an:</value>
|
||||||
@@ -447,4 +447,34 @@
|
|||||||
<data name="DocumentReset" xml:space="preserve">
|
<data name="DocumentReset" xml:space="preserve">
|
||||||
<value>Dokument wurde zurückgesetzt.</value>
|
<value>Dokument wurde zurückgesetzt.</value>
|
||||||
</data>
|
</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>
|
</root>
|
||||||
@@ -447,4 +447,34 @@
|
|||||||
<data name="DocumentReset" xml:space="preserve">
|
<data name="DocumentReset" xml:space="preserve">
|
||||||
<value>Document has been reset.</value>
|
<value>Document has been reset.</value>
|
||||||
</data>
|
</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>
|
</root>
|
||||||
@@ -447,4 +447,34 @@
|
|||||||
<data name="DocumentReset" xml:space="preserve">
|
<data name="DocumentReset" xml:space="preserve">
|
||||||
<value>Le document a été réinitialisé.</value>
|
<value>Le document a été réinitialisé.</value>
|
||||||
</data>
|
</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>
|
</root>
|
||||||
@@ -12,6 +12,7 @@ using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiverReadOnly;
|
|||||||
using EnvelopeGenerator.Application.Common.Extensions;
|
using EnvelopeGenerator.Application.Common.Extensions;
|
||||||
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using EnvelopeGenerator.Domain.Interfaces;
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Application.Services;
|
namespace EnvelopeGenerator.Application.Services;
|
||||||
|
|
||||||
@@ -49,14 +50,33 @@ public class EnvelopeMailService : EmailOutService, IEnvelopeMailService
|
|||||||
_sender = sender;
|
_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)
|
if (accessCode is not null)
|
||||||
_placeholders["[DOCUMENT_ACCESS_CODE]"] = accessCode;
|
_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 sigHost = await _configService.ReadDefaultSignatureHost();
|
||||||
var linkToDoc = $"{sigHost}/EnvelopeKey/{erId}";
|
var linkToDoc = $"{sigHost}/EnvelopeKey/{erId}";
|
||||||
_placeholders["[LINK_TO_DOCUMENT]"] = linkToDoc;
|
_placeholders["[LINK_TO_DOCUMENT]"] = linkToDoc;
|
||||||
@@ -66,6 +86,7 @@ public class EnvelopeMailService : EmailOutService, IEnvelopeMailService
|
|||||||
return _placeholders;
|
return _placeholders;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
private async Task<Dictionary<string, string>> CreatePlaceholders(EnvelopeReceiverReadOnlyDto? readOnlyDto = null)
|
||||||
{
|
{
|
||||||
if (readOnlyDto?.Envelope is not null && readOnlyDto.Receiver is not 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");
|
return acResult.ToFail<int>().Notice(LogLevel.Error, "Therefore, access code cannot be sent");
|
||||||
var accessCode = acResult.Data;
|
var accessCode = acResult.Data;
|
||||||
|
|
||||||
var placeholders = await CreatePlaceholders(accessCode: accessCode, envelopeReceiverDto: dto);
|
var placeholders = await CreatePlaceholders(accessCode: accessCode, er: dto);
|
||||||
|
|
||||||
// Add optional place holders.
|
// Add optional place holders.
|
||||||
if (optionalPlaceholders is not null)
|
if (optionalPlaceholders is not null)
|
||||||
|
|||||||
@@ -487,10 +487,6 @@
|
|||||||
<Project>{6EA0C51F-C2B1-4462-8198-3DE0B32B74F8}</Project>
|
<Project>{6EA0C51F-C2B1-4462-8198-3DE0B32B74F8}</Project>
|
||||||
<Name>EnvelopeGenerator.CommonServices</Name>
|
<Name>EnvelopeGenerator.CommonServices</Name>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
<ProjectReference Include="..\EnvelopeGenerator.CommonServices\EnvelopeGenerator.CommonServices.vbproj">
|
|
||||||
<Project>{6ea0c51f-c2b1-4462-8198-3de0b32b74f8}</Project>
|
|
||||||
<Name>EnvelopeGenerator.CommonServices</Name>
|
|
||||||
</ProjectReference>
|
|
||||||
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj">
|
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj">
|
||||||
<Project>{4f32a98d-e6f0-4a09-bd97-1cf26107e837}</Project>
|
<Project>{4f32a98d-e6f0-4a09-bd97-1cf26107e837}</Project>
|
||||||
<Name>EnvelopeGenerator.Domain</Name>
|
<Name>EnvelopeGenerator.Domain</Name>
|
||||||
|
|||||||
12
EnvelopeGenerator.BBTests/frmFinalizePDF.Designer.vb
generated
12
EnvelopeGenerator.BBTests/frmFinalizePDF.Designer.vb
generated
@@ -27,6 +27,7 @@ Partial Class frmFinalizePDF
|
|||||||
Me.Label2 = New System.Windows.Forms.Label()
|
Me.Label2 = New System.Windows.Forms.Label()
|
||||||
Me.Button1 = New System.Windows.Forms.Button()
|
Me.Button1 = New System.Windows.Forms.Button()
|
||||||
Me.Button2 = New System.Windows.Forms.Button()
|
Me.Button2 = New System.Windows.Forms.Button()
|
||||||
|
Me.Button3 = New System.Windows.Forms.Button()
|
||||||
Me.txtResult = New System.Windows.Forms.TextBox()
|
Me.txtResult = New System.Windows.Forms.TextBox()
|
||||||
Me.txtEnvelope = New System.Windows.Forms.TextBox()
|
Me.txtEnvelope = New System.Windows.Forms.TextBox()
|
||||||
Me.SuspendLayout()
|
Me.SuspendLayout()
|
||||||
@@ -75,6 +76,15 @@ Partial Class frmFinalizePDF
|
|||||||
Me.Button2.Text = "Merge Json"
|
Me.Button2.Text = "Merge Json"
|
||||||
Me.Button2.UseVisualStyleBackColor = True
|
Me.Button2.UseVisualStyleBackColor = True
|
||||||
'
|
'
|
||||||
|
'Button3
|
||||||
|
'
|
||||||
|
Me.Button3.Location = New System.Drawing.Point(15, 160)
|
||||||
|
Me.Button3.Name = "Button3"
|
||||||
|
Me.Button3.Size = New System.Drawing.Size(166, 23)
|
||||||
|
Me.Button3.TabIndex = 5
|
||||||
|
Me.Button3.Text = "Full Finalize Test"
|
||||||
|
Me.Button3.UseVisualStyleBackColor = True
|
||||||
|
'
|
||||||
'txtResult
|
'txtResult
|
||||||
'
|
'
|
||||||
Me.txtResult.Location = New System.Drawing.Point(333, 12)
|
Me.txtResult.Location = New System.Drawing.Point(333, 12)
|
||||||
@@ -97,6 +107,7 @@ Partial Class frmFinalizePDF
|
|||||||
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
|
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
|
||||||
Me.ClientSize = New System.Drawing.Size(800, 450)
|
Me.ClientSize = New System.Drawing.Size(800, 450)
|
||||||
Me.Controls.Add(Me.txtResult)
|
Me.Controls.Add(Me.txtResult)
|
||||||
|
Me.Controls.Add(Me.Button3)
|
||||||
Me.Controls.Add(Me.Button2)
|
Me.Controls.Add(Me.Button2)
|
||||||
Me.Controls.Add(Me.Button1)
|
Me.Controls.Add(Me.Button1)
|
||||||
Me.Controls.Add(Me.Label2)
|
Me.Controls.Add(Me.Label2)
|
||||||
@@ -116,5 +127,6 @@ Partial Class frmFinalizePDF
|
|||||||
Friend WithEvents Label2 As Label
|
Friend WithEvents Label2 As Label
|
||||||
Friend WithEvents Button1 As Button
|
Friend WithEvents Button1 As Button
|
||||||
Friend WithEvents Button2 As Button
|
Friend WithEvents Button2 As Button
|
||||||
|
Friend WithEvents Button3 As Button
|
||||||
Friend WithEvents txtResult As TextBox
|
Friend WithEvents txtResult As TextBox
|
||||||
End Class
|
End Class
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ Imports GdPicture14
|
|||||||
Imports Newtonsoft.Json.Linq
|
Imports Newtonsoft.Json.Linq
|
||||||
Imports EnvelopeGenerator.Infrastructure
|
Imports EnvelopeGenerator.Infrastructure
|
||||||
Imports Microsoft.EntityFrameworkCore
|
Imports Microsoft.EntityFrameworkCore
|
||||||
Imports System.Text
|
|
||||||
Imports DigitalData.Core.Abstractions
|
Imports DigitalData.Core.Abstractions
|
||||||
|
Imports DigitalData.Core.Abstraction.Application.Repository
|
||||||
|
Imports EnvelopeGenerator.Domain.Entities
|
||||||
|
|
||||||
Public Class frmFinalizePDF
|
Public Class frmFinalizePDF
|
||||||
Private Const CONNECTIONSTRING = "Server=sDD-VMP04-SQL17\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=+bk8oAbbQP1AzoHtvZUbd+Mbok2f8Fl4miEx1qssJ5yEaEWoQJ9prg4L14fURpPnqi1WMNs9fE4=;"
|
Private Const CONNECTIONSTRING = "Server=sDD-VMP04-SQL17\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=+bk8oAbbQP1AzoHtvZUbd+Mbok2f8Fl4miEx1qssJ5yEaEWoQJ9prg4L14fURpPnqi1WMNs9fE4=;" + "Encrypt=True;TrustServerCertificate=True;"
|
||||||
|
|
||||||
Private Database As MSSQLServer
|
Private Database As MSSQLServer
|
||||||
Private LogConfig As LogConfig
|
Private LogConfig As LogConfig
|
||||||
@@ -93,8 +94,6 @@ Public Class frmFinalizePDF
|
|||||||
End Function
|
End Function
|
||||||
|
|
||||||
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
|
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
|
||||||
Try
|
|
||||||
|
|
||||||
Dim oTable = LoadAnnotationDataForEnvelope()
|
Dim oTable = LoadAnnotationDataForEnvelope()
|
||||||
Dim oJsonList = oTable.Rows.
|
Dim oJsonList = oTable.Rows.
|
||||||
Cast(Of DataRow).
|
Cast(Of DataRow).
|
||||||
@@ -110,22 +109,9 @@ Public Class frmFinalizePDF
|
|||||||
File.WriteAllBytes(oNewPath, oNewBuffer)
|
File.WriteAllBytes(oNewPath, oNewBuffer)
|
||||||
|
|
||||||
Process.Start(oNewPath)
|
Process.Start(oNewPath)
|
||||||
Catch ex As Exception
|
|
||||||
Dim exMsg As StringBuilder = New StringBuilder(ex.Message).AppendLine()
|
|
||||||
|
|
||||||
Dim innerEx = ex.InnerException
|
|
||||||
While (innerEx IsNot Nothing)
|
|
||||||
exMsg.AppendLine(innerEx.Message)
|
|
||||||
innerEx = innerEx.InnerException
|
|
||||||
End While
|
|
||||||
|
|
||||||
MsgBox(exMsg.ToString(), MsgBoxStyle.Critical)
|
|
||||||
End Try
|
|
||||||
|
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
|
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
|
||||||
Try
|
|
||||||
Dim oTable = LoadAnnotationDataForEnvelope()
|
Dim oTable = LoadAnnotationDataForEnvelope()
|
||||||
Dim oJsonList = oTable.Rows.
|
Dim oJsonList = oTable.Rows.
|
||||||
Cast(Of DataRow).
|
Cast(Of DataRow).
|
||||||
@@ -139,10 +125,87 @@ Public Class frmFinalizePDF
|
|||||||
oJObject1.Merge(oJObject2)
|
oJObject1.Merge(oJObject2)
|
||||||
|
|
||||||
txtResult.Text = oJObject1.ToString()
|
txtResult.Text = oJObject1.ToString()
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
|
||||||
|
Try
|
||||||
|
Dim envelopeId As Integer = CInt(txtEnvelope.Text)
|
||||||
|
Dim log As New System.Text.StringBuilder()
|
||||||
|
|
||||||
|
' 1. Load annotation JSON data (same as Service)
|
||||||
|
Dim oTable = LoadAnnotationDataForEnvelope()
|
||||||
|
Dim oJsonList = oTable.Rows.
|
||||||
|
Cast(Of DataRow).
|
||||||
|
Select(Function(r As DataRow) r.Item("VALUE").ToString()).
|
||||||
|
ToList()
|
||||||
|
log.AppendLine($"Annotation JSON count: {oJsonList.Count}")
|
||||||
|
|
||||||
|
' 2. Load document bytes (same as Service)
|
||||||
|
Dim oBuffer As Byte() = ReadEnvelope(envelopeId)
|
||||||
|
log.AppendLine($"Document bytes: {oBuffer.Length}")
|
||||||
|
|
||||||
|
' 3. Check what BurnAnnotsToPDF will do internally
|
||||||
|
Using scope = Factory.Shared.ScopeFactory.CreateScope()
|
||||||
|
Dim envRepo = scope.ServiceProvider.Repository(Of Envelope)()
|
||||||
|
Dim envelope = envRepo.Where(Function(env) env.Id = envelopeId).FirstOrDefault()
|
||||||
|
|
||||||
|
If envelope Is Nothing Then
|
||||||
|
log.AppendLine("ERROR: Envelope not found in EF Core!")
|
||||||
|
txtResult.Text = log.ToString()
|
||||||
|
Return
|
||||||
|
End If
|
||||||
|
|
||||||
|
log.AppendLine($"Envelope found: Id={envelope.Id}, EnvelopeTypeId={envelope.EnvelopeTypeId}")
|
||||||
|
log.AppendLine($"ReadOnly (IsReadAndConfirm): {envelope.ReadOnly}")
|
||||||
|
|
||||||
|
If envelope.ReadOnly Then
|
||||||
|
log.AppendLine(">>> EARLY RETURN: ReadOnly=True, original PDF returned without burning")
|
||||||
|
txtResult.Text = log.ToString()
|
||||||
|
Return
|
||||||
|
End If
|
||||||
|
|
||||||
|
Dim sigRepo = scope.ServiceProvider.Repository(Of Signature)()
|
||||||
|
Dim elements = sigRepo _
|
||||||
|
.Where(Function(sig) sig.Document.EnvelopeId = envelopeId) _
|
||||||
|
.Include(Function(sig) sig.Annotations) _
|
||||||
|
.ToList()
|
||||||
|
|
||||||
|
log.AppendLine($"Elements (Signature) count: {elements.Count}")
|
||||||
|
|
||||||
|
If elements.Any() Then
|
||||||
|
log.AppendLine(">>> PATH: BurnElementAnnotsToPDF (new element-based path)")
|
||||||
|
For Each elem In elements
|
||||||
|
Dim annotCount = If(elem.Annotations IsNot Nothing, elem.Annotations.Count(), 0)
|
||||||
|
log.AppendLine($" Element Id={elem.Id}, Page={elem.Page}, X={elem.X}, Y={elem.Y}, W={elem.Width}, H={elem.Height}, Annotations={annotCount}")
|
||||||
|
If elem.Annotations IsNot Nothing Then
|
||||||
|
For Each annot In elem.Annotations
|
||||||
|
log.AppendLine($" Annot: Name={annot.Name}, Type={annot.Type}, X={annot.X}, Y={annot.Y}, W={annot.Width}, H={annot.Height}")
|
||||||
|
Next
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
Else
|
||||||
|
log.AppendLine(">>> PATH: BurnInstantJSONAnnotsToPDF (old JSON-based path)")
|
||||||
|
End If
|
||||||
|
End Using
|
||||||
|
|
||||||
|
' 4. Actually call BurnAnnotsToPDF (same as Service)
|
||||||
|
log.AppendLine("")
|
||||||
|
log.AppendLine("Calling BurnAnnotsToPDF...")
|
||||||
|
Dim oNewBuffer = PDFBurner.BurnAnnotsToPDF(oBuffer, oJsonList, envelopeId)
|
||||||
|
log.AppendLine($"Result bytes: {oNewBuffer.Length}")
|
||||||
|
log.AppendLine($"Same as input: {oBuffer.Length = oNewBuffer.Length AndAlso oBuffer.SequenceEqual(oNewBuffer)}")
|
||||||
|
|
||||||
|
' 5. Write output
|
||||||
|
Dim desktopPath As String = Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
|
||||||
|
Dim oNewPath = Path.Combine(desktopPath, $"E{txtEnvelope.Text}_FullTest.burned.pdf")
|
||||||
|
File.WriteAllBytes(oNewPath, oNewBuffer)
|
||||||
|
log.AppendLine($"Output: {oNewPath}")
|
||||||
|
|
||||||
|
txtResult.Text = log.ToString()
|
||||||
|
Process.Start(oNewPath)
|
||||||
|
|
||||||
Catch ex As Exception
|
Catch ex As Exception
|
||||||
MsgBox(ex.Message, MsgBoxStyle.Critical)
|
txtResult.Text = $"ERROR: {ex.Message}{vbCrLf}{vbCrLf}{ex.ToString()}"
|
||||||
End Try
|
End Try
|
||||||
End Sub
|
End Sub
|
||||||
End Class
|
End Class
|
||||||
@@ -15,13 +15,13 @@
|
|||||||
<package id="DigitalData.Modules.Messaging" version="1.9.8" targetFramework="net462" />
|
<package id="DigitalData.Modules.Messaging" version="1.9.8" targetFramework="net462" />
|
||||||
<package id="DocumentFormat.OpenXml" version="3.2.0" targetFramework="net462" />
|
<package id="DocumentFormat.OpenXml" version="3.2.0" targetFramework="net462" />
|
||||||
<package id="DocumentFormat.OpenXml.Framework" version="3.2.0" targetFramework="net462" />
|
<package id="DocumentFormat.OpenXml.Framework" version="3.2.0" targetFramework="net462" />
|
||||||
<package id="EntityFramework" version="6.5.1" targetFramework="net462" />
|
<package id="EntityFramework" version="6.4.4" targetFramework="net462" />
|
||||||
<package id="EntityFramework.Firebird" version="6.4.0" targetFramework="net462" />
|
<package id="EntityFramework.Firebird" version="6.4.0" targetFramework="net462" />
|
||||||
<package id="FirebirdSql.Data.FirebirdClient" version="7.5.0" targetFramework="net462" />
|
<package id="FirebirdSql.Data.FirebirdClient" version="7.5.0" targetFramework="net462" />
|
||||||
<package id="GdPicture" version="14.3.3" targetFramework="net462" />
|
<package id="GdPicture" version="14.3.3" targetFramework="net462" />
|
||||||
<package id="GdPicture.runtimes.windows" version="14.3.3" targetFramework="net462" />
|
<package id="GdPicture.runtimes.windows" version="14.3.3" targetFramework="net462" />
|
||||||
<package id="Microsoft.AspNet.WebApi.Client" version="6.0.0" targetFramework="net462" />
|
<package id="Microsoft.AspNet.WebApi.Client" version="6.0.0" targetFramework="net462" />
|
||||||
<package id="Microsoft.Bcl.AsyncInterfaces" version="9.0.0" targetFramework="net462" />
|
<package id="Microsoft.Bcl.AsyncInterfaces" version="8.0.0" targetFramework="net462" />
|
||||||
<package id="Microsoft.Bcl.Cryptography" version="9.0.0" targetFramework="net462" />
|
<package id="Microsoft.Bcl.Cryptography" version="9.0.0" targetFramework="net462" />
|
||||||
<package id="Microsoft.Bcl.HashCode" version="1.1.1" targetFramework="net462" />
|
<package id="Microsoft.Bcl.HashCode" version="1.1.1" targetFramework="net462" />
|
||||||
<package id="Microsoft.CSharp" version="4.7.0" targetFramework="net462" />
|
<package id="Microsoft.CSharp" version="4.7.0" targetFramework="net462" />
|
||||||
@@ -62,10 +62,10 @@
|
|||||||
<package id="protobuf-net.Core" version="3.2.46" targetFramework="net462" />
|
<package id="protobuf-net.Core" version="3.2.46" targetFramework="net462" />
|
||||||
<package id="RtfPipe" version="2.0.7677.4303" targetFramework="net462" />
|
<package id="RtfPipe" version="2.0.7677.4303" targetFramework="net462" />
|
||||||
<package id="S22.Imap" version="3.6.0.0" targetFramework="net462" />
|
<package id="S22.Imap" version="3.6.0.0" targetFramework="net462" />
|
||||||
<package id="System.Buffers" version="4.6.1" targetFramework="net462" />
|
<package id="System.Buffers" version="4.6.0" targetFramework="net462" />
|
||||||
<package id="System.ClientModel" version="1.8.0" targetFramework="net462" />
|
<package id="System.ClientModel" version="1.8.0" targetFramework="net462" />
|
||||||
<package id="System.CodeDom" version="9.0.0" targetFramework="net462" />
|
<package id="System.CodeDom" version="8.0.0" targetFramework="net462" />
|
||||||
<package id="System.Collections.Immutable" version="9.0.0" targetFramework="net462" />
|
<package id="System.Collections.Immutable" version="8.0.0" targetFramework="net462" />
|
||||||
<package id="System.ComponentModel.Annotations" version="4.7.0" targetFramework="net462" />
|
<package id="System.ComponentModel.Annotations" version="4.7.0" targetFramework="net462" />
|
||||||
<package id="System.Data.Common" version="4.3.0" targetFramework="net462" />
|
<package id="System.Data.Common" version="4.3.0" targetFramework="net462" />
|
||||||
<package id="System.Data.Odbc" version="6.0.1" targetFramework="net462" />
|
<package id="System.Data.Odbc" version="6.0.1" targetFramework="net462" />
|
||||||
@@ -74,23 +74,23 @@
|
|||||||
<package id="System.Formats.Asn1" version="10.0.3" targetFramework="net462" />
|
<package id="System.Formats.Asn1" version="10.0.3" targetFramework="net462" />
|
||||||
<package id="System.IdentityModel.Tokens.Jwt" version="7.7.1" targetFramework="net462" />
|
<package id="System.IdentityModel.Tokens.Jwt" version="7.7.1" targetFramework="net462" />
|
||||||
<package id="System.IO.FileSystem.AccessControl" version="5.0.0" targetFramework="net462" />
|
<package id="System.IO.FileSystem.AccessControl" version="5.0.0" targetFramework="net462" />
|
||||||
<package id="System.IO.Packaging" version="9.0.0" targetFramework="net462" />
|
<package id="System.IO.Packaging" version="8.0.1" targetFramework="net462" />
|
||||||
<package id="System.IO.Pipelines" version="9.0.0" targetFramework="net462" />
|
<package id="System.IO.Pipelines" version="9.0.0" targetFramework="net462" />
|
||||||
<package id="System.Management" version="9.0.0" targetFramework="net462" />
|
<package id="System.Management" version="8.0.0" targetFramework="net462" />
|
||||||
<package id="System.Memory" version="4.6.3" targetFramework="net462" />
|
<package id="System.Memory" version="4.6.0" targetFramework="net462" />
|
||||||
<package id="System.Memory.Data" version="8.0.1" targetFramework="net462" />
|
<package id="System.Memory.Data" version="8.0.1" targetFramework="net462" />
|
||||||
<package id="System.Numerics.Vectors" version="4.6.1" targetFramework="net462" />
|
<package id="System.Numerics.Vectors" version="4.6.0" targetFramework="net462" />
|
||||||
<package id="System.Runtime.CompilerServices.Unsafe" version="6.1.2" targetFramework="net462" />
|
<package id="System.Runtime.CompilerServices.Unsafe" version="6.1.0" targetFramework="net462" />
|
||||||
<package id="System.Security.AccessControl" version="6.0.1" targetFramework="net462" />
|
<package id="System.Security.AccessControl" version="6.0.1" targetFramework="net462" />
|
||||||
<package id="System.Security.Cryptography.Algorithms" version="4.3.1" targetFramework="net462" />
|
<package id="System.Security.Cryptography.Algorithms" version="4.3.1" targetFramework="net462" />
|
||||||
<package id="System.Security.Cryptography.Cng" version="5.0.0" targetFramework="net462" />
|
<package id="System.Security.Cryptography.Cng" version="5.0.0" targetFramework="net462" />
|
||||||
<package id="System.Security.Cryptography.Pkcs" version="9.0.0" targetFramework="net462" />
|
<package id="System.Security.Cryptography.Pkcs" version="8.0.1" targetFramework="net462" />
|
||||||
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net462" />
|
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net462" />
|
||||||
<package id="System.Security.Cryptography.ProtectedData" version="4.5.0" targetFramework="net462" />
|
<package id="System.Security.Cryptography.ProtectedData" version="4.5.0" targetFramework="net462" />
|
||||||
<package id="System.Security.Principal.Windows" version="5.0.0" targetFramework="net462" />
|
<package id="System.Security.Principal.Windows" version="5.0.0" targetFramework="net462" />
|
||||||
<package id="System.Text.Encodings.Web" version="9.0.0" targetFramework="net462" />
|
<package id="System.Text.Encodings.Web" version="8.0.0" targetFramework="net462" />
|
||||||
<package id="System.Text.Json" version="9.0.0" targetFramework="net462" />
|
<package id="System.Text.Json" version="8.0.6" targetFramework="net462" />
|
||||||
<package id="System.Text.RegularExpressions" version="4.3.1" targetFramework="net462" />
|
<package id="System.Text.RegularExpressions" version="4.3.1" targetFramework="net462" />
|
||||||
<package id="System.Threading.Tasks.Extensions" version="4.6.0" targetFramework="net462" />
|
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net462" />
|
||||||
<package id="System.ValueTuple" version="4.6.1" targetFramework="net462" />
|
<package id="System.ValueTuple" version="4.5.0" targetFramework="net462" />
|
||||||
</packages>
|
</packages>
|
||||||
@@ -230,6 +230,29 @@ Namespace Jobs
|
|||||||
|
|
||||||
Return Task.FromResult(True)
|
Return Task.FromResult(True)
|
||||||
End Function
|
End Function
|
||||||
|
|
||||||
|
#Region "From BBTests"
|
||||||
|
Private Function ReadEnvelope(pEnvID As Integer) As Byte()
|
||||||
|
Dim strSql As String = "Select [BYTE_DATA] from [TBSIG_ENVELOPE_DOCUMENT] WHERE ENVELOPE_ID = " & pEnvID
|
||||||
|
Dim obyteDB = Database.GetScalarValue(strSql)
|
||||||
|
If Not IsDBNull(obyteDB) Then
|
||||||
|
Dim fileData As Byte() = DirectCast(Database.GetScalarValue(strSql), Byte())
|
||||||
|
If fileData IsNot Nothing Then
|
||||||
|
Return fileData
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
|
||||||
|
Throw New InvalidOperationException($"Byte data is null. Envelope ID: {pEnvID}")
|
||||||
|
|
||||||
|
End Function
|
||||||
|
|
||||||
|
Private Function LoadAnnotationDataForEnvelope(pEnvID As Integer) As DataTable
|
||||||
|
Dim oSql = $"SELECT VALUE FROM [TBSIG_DOCUMENT_STATUS] WHERE ENVELOPE_ID = {pEnvID}"
|
||||||
|
Return Database.GetDatatable(oSql)
|
||||||
|
|
||||||
|
End Function
|
||||||
|
#End Region
|
||||||
|
|
||||||
Private Sub Update_File_DB(pFilePath As String, pEnvelopeID As Long)
|
Private Sub Update_File_DB(pFilePath As String, pEnvelopeID As Long)
|
||||||
Dim SqlCom As SqlCommand
|
Dim SqlCom As SqlCommand
|
||||||
Dim imageData As Byte()
|
Dim imageData As Byte()
|
||||||
@@ -418,7 +441,18 @@ Namespace Jobs
|
|||||||
End Try
|
End Try
|
||||||
End If
|
End If
|
||||||
|
|
||||||
Return PDFBurner.BurnAnnotsToPDF(oInputDocumentBuffer, oAnnotations, pEnvelopeData.EnvelopeId)
|
#Region "From BBTests"
|
||||||
|
Dim oTable = LoadAnnotationDataForEnvelope(pEnvelopeId)
|
||||||
|
Dim oJsonList = oTable.Rows.
|
||||||
|
Cast(Of DataRow).
|
||||||
|
Select(Function(r As DataRow) r.Item("VALUE").ToString()).
|
||||||
|
ToList()
|
||||||
|
|
||||||
|
Dim oBuffer As Byte() = ReadEnvelope(pEnvelopeId)
|
||||||
|
|
||||||
|
#End Region
|
||||||
|
|
||||||
|
Return PDFBurner.BurnAnnotsToPDF(oBuffer, oJsonList, pEnvelopeId)
|
||||||
End Function
|
End Function
|
||||||
|
|
||||||
Private Function GetEnvelopeData(pEnvelopeId As Integer) As EnvelopeData
|
Private Function GetEnvelopeData(pEnvelopeId As Integer) As EnvelopeData
|
||||||
|
|||||||
@@ -85,16 +85,37 @@ Namespace Jobs.FinalizeDocument
|
|||||||
|
|
||||||
'Add annotations
|
'Add annotations
|
||||||
For Each element In elements
|
For Each element In elements
|
||||||
|
If element Is Nothing Then
|
||||||
|
Continue For
|
||||||
|
End If
|
||||||
|
|
||||||
|
Dim elementAnnotations = If(element.Annotations, Enumerable.Empty(Of ElementAnnotation)())
|
||||||
|
If Not elementAnnotations.Any() Then
|
||||||
|
Continue For
|
||||||
|
End If
|
||||||
|
|
||||||
Dim frameX = (element.Left - 0.7 - margin)
|
Dim frameX = (element.Left - 0.7 - margin)
|
||||||
|
|
||||||
Dim frame = element.Annotations.FirstOrDefault(Function(a) a.Name = "frame")
|
Dim frame = elementAnnotations.FirstOrDefault(Function(a) a.Name = "frame")
|
||||||
Dim frameY = element.Top - 0.5 - margin
|
Dim frameY = element.Top - 0.5 - margin
|
||||||
Dim frameYShift = frame.Y - frameY * inchFactor
|
Dim frameYShift As Double = 0
|
||||||
Dim frameXShift = frame.X - frameX * inchFactor
|
Dim frameXShift As Double = 0
|
||||||
|
|
||||||
|
If frame IsNot Nothing Then
|
||||||
|
frameYShift = frame.Y - frameY * inchFactor
|
||||||
|
frameXShift = frame.X - frameX * inchFactor
|
||||||
|
End If
|
||||||
|
|
||||||
|
For Each annot In elementAnnotations
|
||||||
|
If annot Is Nothing Then
|
||||||
|
Continue For
|
||||||
|
End If
|
||||||
|
|
||||||
|
Dim yOffsetofFF As Double = 0
|
||||||
|
If Not String.IsNullOrEmpty(annot.Name) Then
|
||||||
|
yOffsetsOfFF.TryGetValue(annot.Name, yOffsetofFF)
|
||||||
|
End If
|
||||||
|
|
||||||
For Each annot In element.Annotations
|
|
||||||
Dim yOffsetofFF As Double = If(yOffsetsOfFF.TryGetValue(annot.Name, yOffsetofFF), yOffsetofFF, 0)
|
|
||||||
Dim y = frameY + yOffsetofFF
|
Dim y = frameY + yOffsetofFF
|
||||||
|
|
||||||
If annot.Type = AnnotationType.FormField Then
|
If annot.Type = AnnotationType.FormField Then
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ Public Class PDFMerger
|
|||||||
|
|
||||||
' Convert to PDF/A
|
' Convert to PDF/A
|
||||||
oMergedPDF.ConvertToPDFA(oFinalStream, PDFAConformanceLevel, ALLOW_VECTORIZATION, ALLOW_RASTERIZATION)
|
oMergedPDF.ConvertToPDFA(oFinalStream, PDFAConformanceLevel, ALLOW_VECTORIZATION, ALLOW_RASTERIZATION)
|
||||||
oStatus = oDocumentPDF.GetStat()
|
oStatus = oMergedPDF.GetStat()
|
||||||
If oStatus <> GdPictureStatus.OK Then
|
If oStatus <> GdPictureStatus.OK Then
|
||||||
Throw New MergeDocumentException($"Document could not be converted to PDF/A: {oStatus}")
|
Throw New MergeDocumentException($"Document could not be converted to PDF/A: {oStatus}")
|
||||||
End If
|
End If
|
||||||
|
|||||||
@@ -270,12 +270,26 @@ Public Class ReceiverModel
|
|||||||
Private Function GetSignedDate(pEmailAddress As String, pEnvelopeId As Integer) As Date
|
Private Function GetSignedDate(pEmailAddress As String, pEnvelopeId As Integer) As Date
|
||||||
Try
|
Try
|
||||||
Dim oStatusInt As Integer = EnvelopeStatus.DocumentSigned
|
Dim oStatusInt As Integer = EnvelopeStatus.DocumentSigned
|
||||||
Return Database.GetScalarValue($"SELECT ACTION_DATE FROM [DD_ECM].[dbo].[TBSIG_ENVELOPE_HISTORY] WHERE ENVELOPE_ID = {pEnvelopeId}
|
Dim value = Database.GetScalarValue($"SELECT ACTION_DATE FROM [DD_ECM].[dbo].[TBSIG_ENVELOPE_HISTORY] WHERE ENVELOPE_ID = {pEnvelopeId}
|
||||||
And USER_REFERENCE = '{pEmailAddress}' AND [STATUS] = {oStatusInt}")
|
And USER_REFERENCE = '{pEmailAddress}' AND [STATUS] = {oStatusInt}")
|
||||||
|
|
||||||
|
If value Is Nothing OrElse value Is DBNull.Value Then
|
||||||
|
Return DateTime.MinValue
|
||||||
|
End If
|
||||||
|
|
||||||
|
If TypeOf value Is DateTime Then
|
||||||
|
Return DirectCast(value, DateTime)
|
||||||
|
End If
|
||||||
|
|
||||||
|
Dim parsedDate As DateTime
|
||||||
|
If DateTime.TryParse(value.ToString(), parsedDate) Then
|
||||||
|
Return parsedDate
|
||||||
|
End If
|
||||||
|
|
||||||
|
Return DateTime.MinValue
|
||||||
Catch ex As Exception
|
Catch ex As Exception
|
||||||
Logger.Error(ex)
|
Logger.Error(ex)
|
||||||
Return Nothing
|
Return DateTime.MinValue
|
||||||
End Try
|
End Try
|
||||||
End Function
|
End Function
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
'------------------------------------------------------------------------------
|
'------------------------------------------------------------------------------
|
||||||
' <auto-generated>
|
' <auto-generated>
|
||||||
' Dieser Code wurde von einem Tool generiert.
|
' This code was generated by a tool.
|
||||||
' Laufzeitversion:4.0.30319.42000
|
' Runtime Version:4.0.30319.42000
|
||||||
'
|
'
|
||||||
' Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
|
' Changes to this file may cause incorrect behavior and will be lost if
|
||||||
' der Code erneut generiert wird.
|
' the code is regenerated.
|
||||||
' </auto-generated>
|
' </auto-generated>
|
||||||
'------------------------------------------------------------------------------
|
'------------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -15,12 +15,12 @@ Imports System
|
|||||||
|
|
||||||
Namespace My.Resources
|
Namespace My.Resources
|
||||||
|
|
||||||
'Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert
|
'This class was auto-generated by the StronglyTypedResourceBuilder
|
||||||
'-Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert.
|
'class via a tool like ResGen or Visual Studio.
|
||||||
'Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen
|
'To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
'mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu.
|
'with the /str option, or rebuild your VS project.
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw.
|
''' A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
<Global.System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0"), _
|
<Global.System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0"), _
|
||||||
Global.System.Diagnostics.DebuggerNonUserCodeAttribute(), _
|
Global.System.Diagnostics.DebuggerNonUserCodeAttribute(), _
|
||||||
@@ -37,7 +37,7 @@ Namespace My.Resources
|
|||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird.
|
''' Returns the cached ResourceManager instance used by this class.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
<Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)> _
|
<Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)> _
|
||||||
Public Shared ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager
|
Public Shared ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager
|
||||||
@@ -51,8 +51,8 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle
|
''' Overrides the current thread's CurrentUICulture property for all
|
||||||
''' Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden.
|
''' resource lookups using this strongly typed resource class.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
<Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)> _
|
<Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)> _
|
||||||
Public Shared Property Culture() As Global.System.Globalization.CultureInfo
|
Public Shared Property Culture() As Global.System.Globalization.CultureInfo
|
||||||
@@ -65,7 +65,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Zugriffscode korrekt eingegeben ähnelt.
|
''' Looks up a localized string similar to Zugriffscode korrekt eingegeben.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property AccessCodeCorrect() As String
|
Public Shared ReadOnly Property AccessCodeCorrect() As String
|
||||||
Get
|
Get
|
||||||
@@ -74,7 +74,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Zugriffscode falsch eingegeben ähnelt.
|
''' Looks up a localized string similar to Zugriffscode falsch eingegeben.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property AccessCodeIncorrect() As String
|
Public Shared ReadOnly Property AccessCodeIncorrect() As String
|
||||||
Get
|
Get
|
||||||
@@ -83,7 +83,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Zugriffscode angefordert ähnelt.
|
''' Looks up a localized string similar to Zugriffscode angefordert.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property AccessCodeRequested() As String
|
Public Shared ReadOnly Property AccessCodeRequested() As String
|
||||||
Get
|
Get
|
||||||
@@ -92,7 +92,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Fortgeschrittene Elektronische Signatur ähnelt.
|
''' Looks up a localized string similar to Fortgeschrittene Elektronische Signatur.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property AdvancedElectronicSignature() As String
|
Public Shared ReadOnly Property AdvancedElectronicSignature() As String
|
||||||
Get
|
Get
|
||||||
@@ -101,7 +101,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Abgeschlossen ähnelt.
|
''' Looks up a localized string similar to Abgeschlossen.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property Completed() As String
|
Public Shared ReadOnly Property Completed() As String
|
||||||
Get
|
Get
|
||||||
@@ -110,7 +110,16 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Vollständig Signiert ähnelt.
|
''' Looks up a localized string similar to Vollständig bestätigt.
|
||||||
|
'''</summary>
|
||||||
|
Public Shared ReadOnly Property CompletelyConfirmed() As String
|
||||||
|
Get
|
||||||
|
Return ResourceManager.GetString("CompletelyConfirmed", resourceCulture)
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
|
||||||
|
'''<summary>
|
||||||
|
''' Looks up a localized string similar to Vollständig signiert.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property CompletelySigned() As String
|
Public Shared ReadOnly Property CompletelySigned() As String
|
||||||
Get
|
Get
|
||||||
@@ -119,7 +128,25 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Vertrag ähnelt.
|
''' Looks up a localized string similar to Lesebestätigung.
|
||||||
|
'''</summary>
|
||||||
|
Public Shared ReadOnly Property Confirmation() As String
|
||||||
|
Get
|
||||||
|
Return ResourceManager.GetString("Confirmation", resourceCulture)
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
|
||||||
|
'''<summary>
|
||||||
|
''' Looks up a localized string similar to Gelesen und Bestätigt.
|
||||||
|
'''</summary>
|
||||||
|
Public Shared ReadOnly Property Confirmed() As String
|
||||||
|
Get
|
||||||
|
Return ResourceManager.GetString("Confirmed", resourceCulture)
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
|
||||||
|
'''<summary>
|
||||||
|
''' Looks up a localized string similar to Vertrag.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property Contract() As String
|
Public Shared ReadOnly Property Contract() As String
|
||||||
Get
|
Get
|
||||||
@@ -128,7 +155,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Erstellt ähnelt.
|
''' Looks up a localized string similar to Erstellt.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property Created() As String
|
Public Shared ReadOnly Property Created() As String
|
||||||
Get
|
Get
|
||||||
@@ -137,7 +164,16 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Dokument Rotation geändert ähnelt.
|
''' Looks up a localized string similar to Dokument gelesen und bestätigt.
|
||||||
|
'''</summary>
|
||||||
|
Public Shared ReadOnly Property DocumentConfirmed() As String
|
||||||
|
Get
|
||||||
|
Return ResourceManager.GetString("DocumentConfirmed", resourceCulture)
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
|
||||||
|
'''<summary>
|
||||||
|
''' Looks up a localized string similar to Dokument Rotation geändert.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property DocumentMod_Rotation() As String
|
Public Shared ReadOnly Property DocumentMod_Rotation() As String
|
||||||
Get
|
Get
|
||||||
@@ -146,7 +182,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Dokument geöffnet ähnelt.
|
''' Looks up a localized string similar to Dokument geöffnet.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property DocumentOpened() As String
|
Public Shared ReadOnly Property DocumentOpened() As String
|
||||||
Get
|
Get
|
||||||
@@ -155,7 +191,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Unterzeichnung abgelehnt ähnelt.
|
''' Looks up a localized string similar to Unterzeichnung abgelehnt.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property DocumentRejected() As String
|
Public Shared ReadOnly Property DocumentRejected() As String
|
||||||
Get
|
Get
|
||||||
@@ -164,7 +200,16 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Dokument unterzeichnet ähnelt.
|
''' Looks up a localized string similar to Lesebestätigung abgelehnt.
|
||||||
|
'''</summary>
|
||||||
|
Public Shared ReadOnly Property DocumentRejectedRaC() As String
|
||||||
|
Get
|
||||||
|
Return ResourceManager.GetString("DocumentRejectedRaC", resourceCulture)
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
|
||||||
|
'''<summary>
|
||||||
|
''' Looks up a localized string similar to Dokument unterzeichnet.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property DocumentSigned() As String
|
Public Shared ReadOnly Property DocumentSigned() As String
|
||||||
Get
|
Get
|
||||||
@@ -173,7 +218,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Entwurf ähnelt.
|
''' Looks up a localized string similar to Entwurf.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property Draft() As String
|
Public Shared ReadOnly Property Draft() As String
|
||||||
Get
|
Get
|
||||||
@@ -182,7 +227,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Archiviert ähnelt.
|
''' Looks up a localized string similar to Archiviert.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property EnvelopeArchived() As String
|
Public Shared ReadOnly Property EnvelopeArchived() As String
|
||||||
Get
|
Get
|
||||||
@@ -191,7 +236,16 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Vollständig signiert ähnelt.
|
''' Looks up a localized string similar to Vollständig bestätigt.
|
||||||
|
'''</summary>
|
||||||
|
Public Shared ReadOnly Property EnvelopeCompletelyConfirmed() As String
|
||||||
|
Get
|
||||||
|
Return ResourceManager.GetString("EnvelopeCompletelyConfirmed", resourceCulture)
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
|
||||||
|
'''<summary>
|
||||||
|
''' Looks up a localized string similar to Vollständig signiert.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property EnvelopeCompletelySigned() As String
|
Public Shared ReadOnly Property EnvelopeCompletelySigned() As String
|
||||||
Get
|
Get
|
||||||
@@ -200,7 +254,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Umschlag Erstellt ähnelt.
|
''' Looks up a localized string similar to Umschlag Erstellt.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property EnvelopeCreated() As String
|
Public Shared ReadOnly Property EnvelopeCreated() As String
|
||||||
Get
|
Get
|
||||||
@@ -209,7 +263,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Umschlag Gelöscht ähnelt.
|
''' Looks up a localized string similar to Umschlag Gelöscht.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property EnvelopeDeleted() As String
|
Public Shared ReadOnly Property EnvelopeDeleted() As String
|
||||||
Get
|
Get
|
||||||
@@ -218,7 +272,16 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Teil-Signiert ähnelt.
|
''' Looks up a localized string similar to Teil-Bestätigt.
|
||||||
|
'''</summary>
|
||||||
|
Public Shared ReadOnly Property EnvelopePartlyConfirmed() As String
|
||||||
|
Get
|
||||||
|
Return ResourceManager.GetString("EnvelopePartlyConfirmed", resourceCulture)
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
|
||||||
|
'''<summary>
|
||||||
|
''' Looks up a localized string similar to Teil-Signiert.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property EnvelopePartlySigned() As String
|
Public Shared ReadOnly Property EnvelopePartlySigned() As String
|
||||||
Get
|
Get
|
||||||
@@ -227,7 +290,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Umschlag in Queue ähnelt.
|
''' Looks up a localized string similar to Umschlag in Queue.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property EnvelopeQueued() As String
|
Public Shared ReadOnly Property EnvelopeQueued() As String
|
||||||
Get
|
Get
|
||||||
@@ -236,7 +299,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Umschlag abgelehnt ähnelt.
|
''' Looks up a localized string similar to Umschlag abgelehnt.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property EnvelopeRejected() As String
|
Public Shared ReadOnly Property EnvelopeRejected() As String
|
||||||
Get
|
Get
|
||||||
@@ -245,7 +308,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Signierungszertifikat erstellt ähnelt.
|
''' Looks up a localized string similar to Signierungszertifikat erstellt.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property EnvelopeReportCreated() As String
|
Public Shared ReadOnly Property EnvelopeReportCreated() As String
|
||||||
Get
|
Get
|
||||||
@@ -254,7 +317,16 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Gespeichert ähnelt.
|
''' Looks up a localized string similar to Lesebestätigungszertifikat erstellt.
|
||||||
|
'''</summary>
|
||||||
|
Public Shared ReadOnly Property EnvelopeReportCreatedRaC() As String
|
||||||
|
Get
|
||||||
|
Return ResourceManager.GetString("EnvelopeReportCreatedRaC", resourceCulture)
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
|
||||||
|
'''<summary>
|
||||||
|
''' Looks up a localized string similar to Gespeichert.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property EnvelopeSaved() As String
|
Public Shared ReadOnly Property EnvelopeSaved() As String
|
||||||
Get
|
Get
|
||||||
@@ -263,7 +335,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Gesendet ähnelt.
|
''' Looks up a localized string similar to Gesendet.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property EnvelopeSent() As String
|
Public Shared ReadOnly Property EnvelopeSent() As String
|
||||||
Get
|
Get
|
||||||
@@ -272,7 +344,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Umschlag zurückgezogen ähnelt.
|
''' Looks up a localized string similar to Umschlag zurückgezogen.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property EnvelopeWithdrawn() As String
|
Public Shared ReadOnly Property EnvelopeWithdrawn() As String
|
||||||
Get
|
Get
|
||||||
@@ -281,7 +353,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Zugriffscode versendet ähnelt.
|
''' Looks up a localized string similar to Zugriffscode versendet.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property MessageAccessCodeSent() As String
|
Public Shared ReadOnly Property MessageAccessCodeSent() As String
|
||||||
Get
|
Get
|
||||||
@@ -290,7 +362,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Abschlussemail versendet ähnelt.
|
''' Looks up a localized string similar to Abschlussemail versendet.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property MessageCompletionSent() As String
|
Public Shared ReadOnly Property MessageCompletionSent() As String
|
||||||
Get
|
Get
|
||||||
@@ -299,7 +371,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Signaturbestätigung versendet ähnelt.
|
''' Looks up a localized string similar to Signaturbestätigung versendet.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property MessageConfirmationSent() As String
|
Public Shared ReadOnly Property MessageConfirmationSent() As String
|
||||||
Get
|
Get
|
||||||
@@ -308,7 +380,16 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Löschinformation versendet ähnelt.
|
''' Looks up a localized string similar to Lesebestätigung versendet.
|
||||||
|
'''</summary>
|
||||||
|
Public Shared ReadOnly Property MessageConfirmationSentRaC() As String
|
||||||
|
Get
|
||||||
|
Return ResourceManager.GetString("MessageConfirmationSentRaC", resourceCulture)
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
|
||||||
|
'''<summary>
|
||||||
|
''' Looks up a localized string similar to Löschinformation versendet.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property MessageDeletionSent() As String
|
Public Shared ReadOnly Property MessageDeletionSent() As String
|
||||||
Get
|
Get
|
||||||
@@ -317,7 +398,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Dokumentenlink versendet ähnelt.
|
''' Looks up a localized string similar to Dokumentenlink versendet.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property MessageInvitationSent() As String
|
Public Shared ReadOnly Property MessageInvitationSent() As String
|
||||||
Get
|
Get
|
||||||
@@ -326,7 +407,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Nein ähnelt.
|
''' Looks up a localized string similar to Nein.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property No() As String
|
Public Shared ReadOnly Property No() As String
|
||||||
Get
|
Get
|
||||||
@@ -335,7 +416,16 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Teil-Signiert ähnelt.
|
''' Looks up a localized string similar to Teil-Bestätigt.
|
||||||
|
'''</summary>
|
||||||
|
Public Shared ReadOnly Property PartlyConfirmed() As String
|
||||||
|
Get
|
||||||
|
Return ResourceManager.GetString("PartlyConfirmed", resourceCulture)
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
|
||||||
|
'''<summary>
|
||||||
|
''' Looks up a localized string similar to Teil-Signiert.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property PartlySigned() As String
|
Public Shared ReadOnly Property PartlySigned() As String
|
||||||
Get
|
Get
|
||||||
@@ -344,7 +434,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Qualifizierte Signatur ähnelt.
|
''' Looks up a localized string similar to Qualifizierte Signatur.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property QualifiedSignature() As String
|
Public Shared ReadOnly Property QualifiedSignature() As String
|
||||||
Get
|
Get
|
||||||
@@ -353,7 +443,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Arbeitsanweisung ähnelt.
|
''' Looks up a localized string similar to Lesebestätigung.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property ReadAndSign() As String
|
Public Shared ReadOnly Property ReadAndSign() As String
|
||||||
Get
|
Get
|
||||||
@@ -362,7 +452,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Wollen Sie die 2-Faktor Definition für diesen Empfänger zurücksetzen. Der Empfänger muss sich dann neu identifizieren! ähnelt.
|
''' Looks up a localized string similar to Wollen Sie die 2-Faktor Definition für diesen Empfänger zurücksetzen. Der Empfänger muss sich dann neu identifizieren!.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property ResetTOTPUser() As String
|
Public Shared ReadOnly Property ResetTOTPUser() As String
|
||||||
Get
|
Get
|
||||||
@@ -371,7 +461,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Gespeichert ähnelt.
|
''' Looks up a localized string similar to Gespeichert.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property Saved() As String
|
Public Shared ReadOnly Property Saved() As String
|
||||||
Get
|
Get
|
||||||
@@ -380,7 +470,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Gesendet ähnelt.
|
''' Looks up a localized string similar to Gesendet.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property Sent() As String
|
Public Shared ReadOnly Property Sent() As String
|
||||||
Get
|
Get
|
||||||
@@ -389,7 +479,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Signatur ähnelt.
|
''' Looks up a localized string similar to Signatur.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property Signature() As String
|
Public Shared ReadOnly Property Signature() As String
|
||||||
Get
|
Get
|
||||||
@@ -398,7 +488,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Signatur bestätigt ähnelt.
|
''' Looks up a localized string similar to Abschluss bestätigt.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property SignatureConfirmed() As String
|
Public Shared ReadOnly Property SignatureConfirmed() As String
|
||||||
Get
|
Get
|
||||||
@@ -407,7 +497,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Signiert ähnelt.
|
''' Looks up a localized string similar to Signiert.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property Signed() As String
|
Public Shared ReadOnly Property Signed() As String
|
||||||
Get
|
Get
|
||||||
@@ -416,7 +506,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Erfolgreich! Dialog wird geschlossen. ähnelt.
|
''' Looks up a localized string similar to Erfolgreich! Dialog wird geschlossen..
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property Success_FormClose() As String
|
Public Shared ReadOnly Property Success_FormClose() As String
|
||||||
Get
|
Get
|
||||||
@@ -425,7 +515,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Unsigniert ähnelt.
|
''' Looks up a localized string similar to Unsigniert.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property Unsigned() As String
|
Public Shared ReadOnly Property Unsigned() As String
|
||||||
Get
|
Get
|
||||||
@@ -434,7 +524,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Ja ähnelt.
|
''' Looks up a localized string similar to Ja.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property Yes() As String
|
Public Shared ReadOnly Property Yes() As String
|
||||||
Get
|
Get
|
||||||
@@ -443,7 +533,7 @@ Namespace My.Resources
|
|||||||
End Property
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Sucht eine lokalisierte Zeichenfolge, die Ja, mit Anhang ähnelt.
|
''' Looks up a localized string similar to Ja, mit Anhang.
|
||||||
'''</summary>
|
'''</summary>
|
||||||
Public Shared ReadOnly Property YesWithAttachment() As String
|
Public Shared ReadOnly Property YesWithAttachment() As String
|
||||||
Get
|
Get
|
||||||
|
|||||||
@@ -132,12 +132,27 @@
|
|||||||
<data name="Completed" xml:space="preserve">
|
<data name="Completed" xml:space="preserve">
|
||||||
<value>Completed</value>
|
<value>Completed</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="CompletelyConfirmed" xml:space="preserve">
|
||||||
|
<value>Completely confirmed</value>
|
||||||
|
</data>
|
||||||
|
<data name="CompletelySigned" xml:space="preserve">
|
||||||
|
<value>Completely signed</value>
|
||||||
|
</data>
|
||||||
|
<data name="Confirmation" xml:space="preserve">
|
||||||
|
<value>Read Confirmation</value>
|
||||||
|
</data>
|
||||||
|
<data name="Confirmed" xml:space="preserve">
|
||||||
|
<value>Read and signed</value>
|
||||||
|
</data>
|
||||||
<data name="Contract" xml:space="preserve">
|
<data name="Contract" xml:space="preserve">
|
||||||
<value>Contract</value>
|
<value>Contract</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Created" xml:space="preserve">
|
<data name="Created" xml:space="preserve">
|
||||||
<value>Created</value>
|
<value>Created</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DocumentConfirmed" xml:space="preserve">
|
||||||
|
<value>Document read and confirmed</value>
|
||||||
|
</data>
|
||||||
<data name="DocumentMod_Rotation" xml:space="preserve">
|
<data name="DocumentMod_Rotation" xml:space="preserve">
|
||||||
<value>Document rotation adapted</value>
|
<value>Document rotation adapted</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -147,6 +162,9 @@
|
|||||||
<data name="DocumentRejected" xml:space="preserve">
|
<data name="DocumentRejected" xml:space="preserve">
|
||||||
<value>Signing rejected</value>
|
<value>Signing rejected</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DocumentRejectedRaC" xml:space="preserve">
|
||||||
|
<value>Read confirmation rejected</value>
|
||||||
|
</data>
|
||||||
<data name="DocumentSigned" xml:space="preserve">
|
<data name="DocumentSigned" xml:space="preserve">
|
||||||
<value>Document signed</value>
|
<value>Document signed</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -156,6 +174,9 @@
|
|||||||
<data name="EnvelopeArchived" xml:space="preserve">
|
<data name="EnvelopeArchived" xml:space="preserve">
|
||||||
<value>Archived</value>
|
<value>Archived</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="EnvelopeCompletelyConfirmed" xml:space="preserve">
|
||||||
|
<value>Completely confirmed</value>
|
||||||
|
</data>
|
||||||
<data name="EnvelopeCompletelySigned" xml:space="preserve">
|
<data name="EnvelopeCompletelySigned" xml:space="preserve">
|
||||||
<value>Completely signed</value>
|
<value>Completely signed</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -165,8 +186,11 @@
|
|||||||
<data name="EnvelopeDeleted" xml:space="preserve">
|
<data name="EnvelopeDeleted" xml:space="preserve">
|
||||||
<value>Envelope Deleted</value>
|
<value>Envelope Deleted</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="EnvelopePartlyConfirmed" xml:space="preserve">
|
||||||
|
<value>Partially confirmed</value>
|
||||||
|
</data>
|
||||||
<data name="EnvelopePartlySigned" xml:space="preserve">
|
<data name="EnvelopePartlySigned" xml:space="preserve">
|
||||||
<value>Partly signed</value>
|
<value>Partially signed</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EnvelopeQueued" xml:space="preserve">
|
<data name="EnvelopeQueued" xml:space="preserve">
|
||||||
<value>Envelope Queued</value>
|
<value>Envelope Queued</value>
|
||||||
@@ -177,6 +201,9 @@
|
|||||||
<data name="EnvelopeReportCreated" xml:space="preserve">
|
<data name="EnvelopeReportCreated" xml:space="preserve">
|
||||||
<value>Signature certificate created</value>
|
<value>Signature certificate created</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="EnvelopeReportCreatedRaC" xml:space="preserve">
|
||||||
|
<value>Read Confirmation Certificate Created</value>
|
||||||
|
</data>
|
||||||
<data name="EnvelopeSaved" xml:space="preserve">
|
<data name="EnvelopeSaved" xml:space="preserve">
|
||||||
<value>Saved</value>
|
<value>Saved</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -195,6 +222,9 @@
|
|||||||
<data name="MessageConfirmationSent" xml:space="preserve">
|
<data name="MessageConfirmationSent" xml:space="preserve">
|
||||||
<value>Confirmation Sent</value>
|
<value>Confirmation Sent</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="MessageConfirmationSentRaC" xml:space="preserve">
|
||||||
|
<value>Read Confirmation Sent</value>
|
||||||
|
</data>
|
||||||
<data name="MessageDeletionSent" xml:space="preserve">
|
<data name="MessageDeletionSent" xml:space="preserve">
|
||||||
<value>Deletion Notice Sent</value>
|
<value>Deletion Notice Sent</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -204,6 +234,12 @@
|
|||||||
<data name="No" xml:space="preserve">
|
<data name="No" xml:space="preserve">
|
||||||
<value>No</value>
|
<value>No</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PartlyConfirmed" xml:space="preserve">
|
||||||
|
<value>Partially confirmed</value>
|
||||||
|
</data>
|
||||||
|
<data name="PartlySigned" xml:space="preserve">
|
||||||
|
<value>Partially signed</value>
|
||||||
|
</data>
|
||||||
<data name="QualifiedSignature" xml:space="preserve">
|
<data name="QualifiedSignature" xml:space="preserve">
|
||||||
<value>Qualified Signature</value>
|
<value>Qualified Signature</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -223,7 +259,7 @@
|
|||||||
<value>Signature</value>
|
<value>Signature</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SignatureConfirmed" xml:space="preserve">
|
<data name="SignatureConfirmed" xml:space="preserve">
|
||||||
<value>Signature confirmed</value>
|
<value>Finalization confirmed</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Signed" xml:space="preserve">
|
<data name="Signed" xml:space="preserve">
|
||||||
<value>Signed</value>
|
<value>Signed</value>
|
||||||
|
|||||||
@@ -132,8 +132,17 @@
|
|||||||
<data name="Completed" xml:space="preserve">
|
<data name="Completed" xml:space="preserve">
|
||||||
<value>Abgeschlossen</value>
|
<value>Abgeschlossen</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="CompletelyConfirmed" xml:space="preserve">
|
||||||
|
<value>Vollständig bestätigt</value>
|
||||||
|
</data>
|
||||||
<data name="CompletelySigned" xml:space="preserve">
|
<data name="CompletelySigned" xml:space="preserve">
|
||||||
<value>Vollständig Signiert</value>
|
<value>Vollständig signiert</value>
|
||||||
|
</data>
|
||||||
|
<data name="Confirmation" xml:space="preserve">
|
||||||
|
<value>Lesebestätigung</value>
|
||||||
|
</data>
|
||||||
|
<data name="Confirmed" xml:space="preserve">
|
||||||
|
<value>Gelesen und Bestätigt</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Contract" xml:space="preserve">
|
<data name="Contract" xml:space="preserve">
|
||||||
<value>Vertrag</value>
|
<value>Vertrag</value>
|
||||||
@@ -141,6 +150,9 @@
|
|||||||
<data name="Created" xml:space="preserve">
|
<data name="Created" xml:space="preserve">
|
||||||
<value>Erstellt</value>
|
<value>Erstellt</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DocumentConfirmed" xml:space="preserve">
|
||||||
|
<value>Dokument gelesen und bestätigt</value>
|
||||||
|
</data>
|
||||||
<data name="DocumentMod_Rotation" xml:space="preserve">
|
<data name="DocumentMod_Rotation" xml:space="preserve">
|
||||||
<value>Dokument Rotation geändert</value>
|
<value>Dokument Rotation geändert</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -150,6 +162,9 @@
|
|||||||
<data name="DocumentRejected" xml:space="preserve">
|
<data name="DocumentRejected" xml:space="preserve">
|
||||||
<value>Unterzeichnung abgelehnt</value>
|
<value>Unterzeichnung abgelehnt</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DocumentRejectedRaC" xml:space="preserve">
|
||||||
|
<value>Lesebestätigung abgelehnt</value>
|
||||||
|
</data>
|
||||||
<data name="DocumentSigned" xml:space="preserve">
|
<data name="DocumentSigned" xml:space="preserve">
|
||||||
<value>Dokument unterzeichnet</value>
|
<value>Dokument unterzeichnet</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -159,6 +174,9 @@
|
|||||||
<data name="EnvelopeArchived" xml:space="preserve">
|
<data name="EnvelopeArchived" xml:space="preserve">
|
||||||
<value>Archiviert</value>
|
<value>Archiviert</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="EnvelopeCompletelyConfirmed" xml:space="preserve">
|
||||||
|
<value>Vollständig bestätigt</value>
|
||||||
|
</data>
|
||||||
<data name="EnvelopeCompletelySigned" xml:space="preserve">
|
<data name="EnvelopeCompletelySigned" xml:space="preserve">
|
||||||
<value>Vollständig signiert</value>
|
<value>Vollständig signiert</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -168,6 +186,9 @@
|
|||||||
<data name="EnvelopeDeleted" xml:space="preserve">
|
<data name="EnvelopeDeleted" xml:space="preserve">
|
||||||
<value>Umschlag Gelöscht</value>
|
<value>Umschlag Gelöscht</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="EnvelopePartlyConfirmed" xml:space="preserve">
|
||||||
|
<value>Teil-Bestätigt</value>
|
||||||
|
</data>
|
||||||
<data name="EnvelopePartlySigned" xml:space="preserve">
|
<data name="EnvelopePartlySigned" xml:space="preserve">
|
||||||
<value>Teil-Signiert</value>
|
<value>Teil-Signiert</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -180,6 +201,9 @@
|
|||||||
<data name="EnvelopeReportCreated" xml:space="preserve">
|
<data name="EnvelopeReportCreated" xml:space="preserve">
|
||||||
<value>Signierungszertifikat erstellt</value>
|
<value>Signierungszertifikat erstellt</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="EnvelopeReportCreatedRaC" xml:space="preserve">
|
||||||
|
<value>Lesebestätigungszertifikat erstellt</value>
|
||||||
|
</data>
|
||||||
<data name="EnvelopeSaved" xml:space="preserve">
|
<data name="EnvelopeSaved" xml:space="preserve">
|
||||||
<value>Gespeichert</value>
|
<value>Gespeichert</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -198,6 +222,9 @@
|
|||||||
<data name="MessageConfirmationSent" xml:space="preserve">
|
<data name="MessageConfirmationSent" xml:space="preserve">
|
||||||
<value>Signaturbestätigung versendet</value>
|
<value>Signaturbestätigung versendet</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="MessageConfirmationSentRaC" xml:space="preserve">
|
||||||
|
<value>Lesebestätigung versendet</value>
|
||||||
|
</data>
|
||||||
<data name="MessageDeletionSent" xml:space="preserve">
|
<data name="MessageDeletionSent" xml:space="preserve">
|
||||||
<value>Löschinformation versendet</value>
|
<value>Löschinformation versendet</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -207,6 +234,9 @@
|
|||||||
<data name="No" xml:space="preserve">
|
<data name="No" xml:space="preserve">
|
||||||
<value>Nein</value>
|
<value>Nein</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PartlyConfirmed" xml:space="preserve">
|
||||||
|
<value>Teil-Bestätigt</value>
|
||||||
|
</data>
|
||||||
<data name="PartlySigned" xml:space="preserve">
|
<data name="PartlySigned" xml:space="preserve">
|
||||||
<value>Teil-Signiert</value>
|
<value>Teil-Signiert</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -214,7 +244,7 @@
|
|||||||
<value>Qualifizierte Signatur</value>
|
<value>Qualifizierte Signatur</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ReadAndSign" xml:space="preserve">
|
<data name="ReadAndSign" xml:space="preserve">
|
||||||
<value>Arbeitsanweisung</value>
|
<value>Lesebestätigung</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ResetTOTPUser" xml:space="preserve">
|
<data name="ResetTOTPUser" xml:space="preserve">
|
||||||
<value>Wollen Sie die 2-Faktor Definition für diesen Empfänger zurücksetzen. Der Empfänger muss sich dann neu identifizieren!</value>
|
<value>Wollen Sie die 2-Faktor Definition für diesen Empfänger zurücksetzen. Der Empfänger muss sich dann neu identifizieren!</value>
|
||||||
@@ -229,7 +259,7 @@
|
|||||||
<value>Signatur</value>
|
<value>Signatur</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SignatureConfirmed" xml:space="preserve">
|
<data name="SignatureConfirmed" xml:space="preserve">
|
||||||
<value>Signatur bestätigt</value>
|
<value>Abschluss bestätigt</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Signed" xml:space="preserve">
|
<data name="Signed" xml:space="preserve">
|
||||||
<value>Signiert</value>
|
<value>Signiert</value>
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ namespace EnvelopeGenerator.Domain.Entities
|
|||||||
[Column("STATUS")]
|
[Column("STATUS")]
|
||||||
public Constants.DocumentStatus Status { get; set; }
|
public Constants.DocumentStatus Status { get; set; }
|
||||||
|
|
||||||
|
[Column("STATUS_CHANGED_WHEN", TypeName = "datetime")]
|
||||||
|
public DateTime? StatusChangedWhen { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[Column("ADDED_WHEN", TypeName = "datetime")]
|
[Column("ADDED_WHEN", TypeName = "datetime")]
|
||||||
public DateTime AddedWhen { get; set; }
|
public DateTime AddedWhen { get; set; }
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.ComponentModel.DataAnnotations.Schema;
|
|||||||
using EnvelopeGenerator.Domain.Constants;
|
using EnvelopeGenerator.Domain.Constants;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using EnvelopeGenerator.Domain.Interfaces.Auditing;
|
using EnvelopeGenerator.Domain.Interfaces.Auditing;
|
||||||
|
using EnvelopeGenerator.Domain.Interfaces;
|
||||||
#if NETFRAMEWORK
|
#if NETFRAMEWORK
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -13,7 +14,7 @@ using System.Linq;
|
|||||||
namespace EnvelopeGenerator.Domain.Entities
|
namespace EnvelopeGenerator.Domain.Entities
|
||||||
{
|
{
|
||||||
[Table("TBSIG_ENVELOPE", Schema = "dbo")]
|
[Table("TBSIG_ENVELOPE", Schema = "dbo")]
|
||||||
public class Envelope : IHasAddedWhen, IHasChangedWhen
|
public class Envelope : IHasAddedWhen, IHasChangedWhen, IEnvelope
|
||||||
{
|
{
|
||||||
public Envelope()
|
public Envelope()
|
||||||
{
|
{
|
||||||
@@ -106,7 +107,8 @@ namespace EnvelopeGenerator.Domain.Entities
|
|||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
[NotMapped]
|
[NotMapped]
|
||||||
public bool ReadOnly => EnvelopeTypeId == 2;
|
[Obsolete("Use EnvelopeGenerator.Domain.Interfaces.EnvelopeExtensions.IsReadAndConfirm extension method instead.")]
|
||||||
|
public bool ReadOnly => this.IsReadAndConfirm();
|
||||||
|
|
||||||
[Column("CERTIFICATION_TYPE")]
|
[Column("CERTIFICATION_TYPE")]
|
||||||
public int? CertificationType { get; set; }
|
public int? CertificationType { get; set; }
|
||||||
|
|||||||
@@ -35,7 +35,10 @@ namespace EnvelopeGenerator.Domain.Entities
|
|||||||
public DateTime AddedWhen { get; set; }
|
public DateTime AddedWhen { get; set; }
|
||||||
|
|
||||||
[Column("ACTION_DATE", TypeName = "datetime")]
|
[Column("ACTION_DATE", TypeName = "datetime")]
|
||||||
public DateTime? ChangedWhen { get; set; }
|
public DateTime? ActionDate { get; set; }
|
||||||
|
|
||||||
|
[NotMapped]
|
||||||
|
public DateTime? ChangedWhen { get => ActionDate; set => ActionDate = value; }
|
||||||
|
|
||||||
[Column("COMMENT", TypeName = "nvarchar(max)")]
|
[Column("COMMENT", TypeName = "nvarchar(max)")]
|
||||||
public string
|
public string
|
||||||
|
|||||||
20
EnvelopeGenerator.Domain/Interfaces/IEnvelope.cs
Normal file
20
EnvelopeGenerator.Domain/Interfaces/IEnvelope.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsReadAndSign(this IEnvelope envelope)
|
||||||
|
{
|
||||||
|
return envelope.EnvelopeTypeId != 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
200
EnvelopeGenerator.Domain/Resources/Model.Designer.cs
generated
200
EnvelopeGenerator.Domain/Resources/Model.Designer.cs
generated
@@ -1,10 +1,10 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// <auto-generated>
|
// <auto-generated>
|
||||||
// Dieser Code wurde von einem Tool generiert.
|
// This code was generated by a tool.
|
||||||
// Laufzeitversion:4.0.30319.42000
|
// Runtime Version:4.0.30319.42000
|
||||||
//
|
//
|
||||||
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
// der Code erneut generiert wird.
|
// the code is regenerated.
|
||||||
// </auto-generated>
|
// </auto-generated>
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -13,12 +13,12 @@ namespace My.Resources {
|
|||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw.
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// Diese Klasse wurde von der StronglyTypedResourceBuilder automatisch generiert
|
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||||
// -Klasse über ein Tool wie ResGen oder Visual Studio automatisch generiert.
|
// class via a tool like ResGen or Visual Studio.
|
||||||
// Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
// mit der /str-Option erneut aus, oder Sie erstellen Ihr VS-Projekt neu.
|
// with the /str option, or rebuild your VS project.
|
||||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
@@ -33,7 +33,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird.
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
public static global::System.Resources.ResourceManager ResourceManager {
|
public static global::System.Resources.ResourceManager ResourceManager {
|
||||||
@@ -47,8 +47,8 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
/// Ressourcenzuordnungen, die diese stark typisierte Ressourcenklasse verwenden.
|
/// resource lookups using this strongly typed resource class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
public static global::System.Globalization.CultureInfo Culture {
|
public static global::System.Globalization.CultureInfo Culture {
|
||||||
@@ -61,7 +61,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Zugriffscode korrekt eingegeben ähnelt.
|
/// Looks up a localized string similar to Zugriffscode korrekt eingegeben.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string AccessCodeCorrect {
|
public static string AccessCodeCorrect {
|
||||||
get {
|
get {
|
||||||
@@ -70,7 +70,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Zugriffscode falsch eingegeben ähnelt.
|
/// Looks up a localized string similar to Zugriffscode falsch eingegeben.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string AccessCodeIncorrect {
|
public static string AccessCodeIncorrect {
|
||||||
get {
|
get {
|
||||||
@@ -79,7 +79,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Zugriffscode angefordert ähnelt.
|
/// Looks up a localized string similar to Zugriffscode angefordert.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string AccessCodeRequested {
|
public static string AccessCodeRequested {
|
||||||
get {
|
get {
|
||||||
@@ -88,7 +88,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Fortgeschrittene Elektronische Signatur ähnelt.
|
/// Looks up a localized string similar to Fortgeschrittene Elektronische Signatur.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string AdvancedElectronicSignature {
|
public static string AdvancedElectronicSignature {
|
||||||
get {
|
get {
|
||||||
@@ -97,7 +97,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Abgeschlossen ähnelt.
|
/// Looks up a localized string similar to Abgeschlossen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Completed {
|
public static string Completed {
|
||||||
get {
|
get {
|
||||||
@@ -106,7 +106,16 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Vollständig Signiert ähnelt.
|
/// Looks up a localized string similar to Vollständig bestätigt.
|
||||||
|
/// </summary>
|
||||||
|
public static string CompletelyConfirmed {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("CompletelyConfirmed", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Vollständig signiert.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string CompletelySigned {
|
public static string CompletelySigned {
|
||||||
get {
|
get {
|
||||||
@@ -115,7 +124,25 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Vertrag ähnelt.
|
/// Looks up a localized string similar to Lesebestätigung.
|
||||||
|
/// </summary>
|
||||||
|
public static string Confirmation {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Confirmation", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Gelesen und bestätigt.
|
||||||
|
/// </summary>
|
||||||
|
public static string Confirmed {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Confirmed", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Vertrag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Contract {
|
public static string Contract {
|
||||||
get {
|
get {
|
||||||
@@ -124,7 +151,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Erstellt ähnelt.
|
/// Looks up a localized string similar to Erstellt.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Created {
|
public static string Created {
|
||||||
get {
|
get {
|
||||||
@@ -133,7 +160,16 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Dokument Rotation geändert ähnelt.
|
/// Looks up a localized string similar to Dokument gelesen und bestätigt.
|
||||||
|
/// </summary>
|
||||||
|
public static string DocumentConfirmed {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("DocumentConfirmed", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Dokument Rotation geändert.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string DocumentMod_Rotation {
|
public static string DocumentMod_Rotation {
|
||||||
get {
|
get {
|
||||||
@@ -142,7 +178,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Dokument geöffnet ähnelt.
|
/// Looks up a localized string similar to Dokument geöffnet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string DocumentOpened {
|
public static string DocumentOpened {
|
||||||
get {
|
get {
|
||||||
@@ -151,7 +187,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Unterzeichnung abgelehnt ähnelt.
|
/// Looks up a localized string similar to Unterzeichnung abgelehnt.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string DocumentRejected {
|
public static string DocumentRejected {
|
||||||
get {
|
get {
|
||||||
@@ -160,7 +196,16 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Dokument unterzeichnet ähnelt.
|
/// Looks up a localized string similar to Lesebestätigung abgelehnt.
|
||||||
|
/// </summary>
|
||||||
|
public static string DocumentRejectedRaC {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("DocumentRejectedRaC", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Dokument unterzeichnet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string DocumentSigned {
|
public static string DocumentSigned {
|
||||||
get {
|
get {
|
||||||
@@ -169,7 +214,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Entwurf ähnelt.
|
/// Looks up a localized string similar to Entwurf.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Draft {
|
public static string Draft {
|
||||||
get {
|
get {
|
||||||
@@ -178,7 +223,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Archiviert ähnelt.
|
/// Looks up a localized string similar to Archiviert.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string EnvelopeArchived {
|
public static string EnvelopeArchived {
|
||||||
get {
|
get {
|
||||||
@@ -187,7 +232,16 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Vollständig signiert ähnelt.
|
/// Looks up a localized string similar to Vollständig gelesen und bestätigt.
|
||||||
|
/// </summary>
|
||||||
|
public static string EnvelopeCompletelyConfirmed {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("EnvelopeCompletelyConfirmed", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Vollständig signiert.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string EnvelopeCompletelySigned {
|
public static string EnvelopeCompletelySigned {
|
||||||
get {
|
get {
|
||||||
@@ -196,7 +250,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Umschlag Erstellt ähnelt.
|
/// Looks up a localized string similar to Umschlag Erstellt.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string EnvelopeCreated {
|
public static string EnvelopeCreated {
|
||||||
get {
|
get {
|
||||||
@@ -205,7 +259,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Umschlag Gelöscht ähnelt.
|
/// Looks up a localized string similar to Umschlag Gelöscht.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string EnvelopeDeleted {
|
public static string EnvelopeDeleted {
|
||||||
get {
|
get {
|
||||||
@@ -214,7 +268,16 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Teil-Signiert ähnelt.
|
/// Looks up a localized string similar to Teil-Bestätigt.
|
||||||
|
/// </summary>
|
||||||
|
public static string EnvelopePartlyConfirmed {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("EnvelopePartlyConfirmed", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Teil-Signiert.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string EnvelopePartlySigned {
|
public static string EnvelopePartlySigned {
|
||||||
get {
|
get {
|
||||||
@@ -223,7 +286,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Umschlag in Queue ähnelt.
|
/// Looks up a localized string similar to Umschlag in Queue.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string EnvelopeQueued {
|
public static string EnvelopeQueued {
|
||||||
get {
|
get {
|
||||||
@@ -232,7 +295,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Umschlag abgelehnt ähnelt.
|
/// Looks up a localized string similar to Umschlag abgelehnt.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string EnvelopeRejected {
|
public static string EnvelopeRejected {
|
||||||
get {
|
get {
|
||||||
@@ -241,7 +304,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Signierungszertifikat erstellt ähnelt.
|
/// Looks up a localized string similar to Signierungszertifikat erstellt.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string EnvelopeReportCreated {
|
public static string EnvelopeReportCreated {
|
||||||
get {
|
get {
|
||||||
@@ -250,7 +313,16 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Gespeichert ähnelt.
|
/// Looks up a localized string similar to Lesebestätigungszertifikat erstellt.
|
||||||
|
/// </summary>
|
||||||
|
public static string EnvelopeReportCreatedRaC {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("EnvelopeReportCreatedRaC", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Gespeichert.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string EnvelopeSaved {
|
public static string EnvelopeSaved {
|
||||||
get {
|
get {
|
||||||
@@ -259,7 +331,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Gesendet ähnelt.
|
/// Looks up a localized string similar to Gesendet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string EnvelopeSent {
|
public static string EnvelopeSent {
|
||||||
get {
|
get {
|
||||||
@@ -268,7 +340,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Umschlag zurückgezogen ähnelt.
|
/// Looks up a localized string similar to Umschlag zurückgezogen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string EnvelopeWithdrawn {
|
public static string EnvelopeWithdrawn {
|
||||||
get {
|
get {
|
||||||
@@ -277,7 +349,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Zugriffscode versendet ähnelt.
|
/// Looks up a localized string similar to Zugriffscode versendet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string MessageAccessCodeSent {
|
public static string MessageAccessCodeSent {
|
||||||
get {
|
get {
|
||||||
@@ -286,7 +358,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Abschlussemail versendet ähnelt.
|
/// Looks up a localized string similar to Abschlussemail versendet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string MessageCompletionSent {
|
public static string MessageCompletionSent {
|
||||||
get {
|
get {
|
||||||
@@ -295,7 +367,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Signaturbestätigung versendet ähnelt.
|
/// Looks up a localized string similar to Abschlussbestätigung versendet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string MessageConfirmationSent {
|
public static string MessageConfirmationSent {
|
||||||
get {
|
get {
|
||||||
@@ -304,7 +376,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Löschinformation versendet ähnelt.
|
/// Looks up a localized string similar to Löschinformation versendet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string MessageDeletionSent {
|
public static string MessageDeletionSent {
|
||||||
get {
|
get {
|
||||||
@@ -313,7 +385,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Dokumentenlink versendet ähnelt.
|
/// Looks up a localized string similar to Dokumentenlink versendet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string MessageInvitationSent {
|
public static string MessageInvitationSent {
|
||||||
get {
|
get {
|
||||||
@@ -322,7 +394,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Nein ähnelt.
|
/// Looks up a localized string similar to Nein.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string No {
|
public static string No {
|
||||||
get {
|
get {
|
||||||
@@ -331,7 +403,16 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Teil-Signiert ähnelt.
|
/// Looks up a localized string similar to Teil-Bestätigt.
|
||||||
|
/// </summary>
|
||||||
|
public static string PartlyConfirmed {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PartlyConfirmed", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Teil-Signiert.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string PartlySigned {
|
public static string PartlySigned {
|
||||||
get {
|
get {
|
||||||
@@ -340,7 +421,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Qualifizierte Signatur ähnelt.
|
/// Looks up a localized string similar to Qualifizierte Signatur.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string QualifiedSignature {
|
public static string QualifiedSignature {
|
||||||
get {
|
get {
|
||||||
@@ -349,7 +430,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Arbeitsanweisung ähnelt.
|
/// Looks up a localized string similar to Lesebestätigung.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string ReadAndSign {
|
public static string ReadAndSign {
|
||||||
get {
|
get {
|
||||||
@@ -358,7 +439,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Wollen Sie die 2-Faktor Definition für diesen Empfänger zurücksetzen. Der Empfänger muss sich dann neu identifizieren! ähnelt.
|
/// Looks up a localized string similar to Wollen Sie die 2-Faktor Definition für diesen Empfänger zurücksetzen. Der Empfänger muss sich dann neu identifizieren!.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string ResetTOTPUser {
|
public static string ResetTOTPUser {
|
||||||
get {
|
get {
|
||||||
@@ -367,7 +448,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Gespeichert ähnelt.
|
/// Looks up a localized string similar to Gespeichert.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Saved {
|
public static string Saved {
|
||||||
get {
|
get {
|
||||||
@@ -376,7 +457,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Gesendet ähnelt.
|
/// Looks up a localized string similar to Gesendet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Sent {
|
public static string Sent {
|
||||||
get {
|
get {
|
||||||
@@ -385,7 +466,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Signatur ähnelt.
|
/// Looks up a localized string similar to Signatur.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Signature {
|
public static string Signature {
|
||||||
get {
|
get {
|
||||||
@@ -394,7 +475,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Signatur bestätigt ähnelt.
|
/// Looks up a localized string similar to Abschluss bestätigt.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string SignatureConfirmed {
|
public static string SignatureConfirmed {
|
||||||
get {
|
get {
|
||||||
@@ -403,7 +484,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Signiert ähnelt.
|
/// Looks up a localized string similar to Signiert.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Signed {
|
public static string Signed {
|
||||||
get {
|
get {
|
||||||
@@ -412,7 +493,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Erfolgreich! Dialog wird geschlossen. ähnelt.
|
/// Looks up a localized string similar to Erfolgreich! Dialog wird geschlossen..
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Success_FormClose {
|
public static string Success_FormClose {
|
||||||
get {
|
get {
|
||||||
@@ -421,7 +502,16 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Unsigniert ähnelt.
|
/// Looks up a localized string similar to Unbestätigt.
|
||||||
|
/// </summary>
|
||||||
|
public static string Unconfirmed {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Unconfirmed", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Unsigniert.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Unsigned {
|
public static string Unsigned {
|
||||||
get {
|
get {
|
||||||
@@ -430,7 +520,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Ja ähnelt.
|
/// Looks up a localized string similar to Ja.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Yes {
|
public static string Yes {
|
||||||
get {
|
get {
|
||||||
@@ -439,7 +529,7 @@ namespace My.Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sucht eine lokalisierte Zeichenfolge, die Ja, mit Anhang ähnelt.
|
/// Looks up a localized string similar to Ja, mit Anhang.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string YesWithAttachment {
|
public static string YesWithAttachment {
|
||||||
get {
|
get {
|
||||||
|
|||||||
@@ -132,15 +132,27 @@
|
|||||||
<data name="Completed" xml:space="preserve">
|
<data name="Completed" xml:space="preserve">
|
||||||
<value>Completed</value>
|
<value>Completed</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="CompletelyConfirmed" xml:space="preserve">
|
||||||
|
<value>Completely confirmed</value>
|
||||||
|
</data>
|
||||||
<data name="CompletelySigned" xml:space="preserve">
|
<data name="CompletelySigned" xml:space="preserve">
|
||||||
<value>Completely signed</value>
|
<value>Completely signed</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Confirmation" xml:space="preserve">
|
||||||
|
<value>Read Confirmation</value>
|
||||||
|
</data>
|
||||||
|
<data name="Confirmed" xml:space="preserve">
|
||||||
|
<value>Read and signed</value>
|
||||||
|
</data>
|
||||||
<data name="Contract" xml:space="preserve">
|
<data name="Contract" xml:space="preserve">
|
||||||
<value>Contract</value>
|
<value>Contract</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Created" xml:space="preserve">
|
<data name="Created" xml:space="preserve">
|
||||||
<value>Created</value>
|
<value>Created</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DocumentConfirmed" xml:space="preserve">
|
||||||
|
<value>Document read and signed</value>
|
||||||
|
</data>
|
||||||
<data name="DocumentMod_Rotation" xml:space="preserve">
|
<data name="DocumentMod_Rotation" xml:space="preserve">
|
||||||
<value>Document rotation adapted</value>
|
<value>Document rotation adapted</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -150,6 +162,9 @@
|
|||||||
<data name="DocumentRejected" xml:space="preserve">
|
<data name="DocumentRejected" xml:space="preserve">
|
||||||
<value>Signing rejected</value>
|
<value>Signing rejected</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DocumentRejectedRaC" xml:space="preserve">
|
||||||
|
<value>Read confirmation rejected</value>
|
||||||
|
</data>
|
||||||
<data name="DocumentSigned" xml:space="preserve">
|
<data name="DocumentSigned" xml:space="preserve">
|
||||||
<value>Document signed</value>
|
<value>Document signed</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -159,6 +174,9 @@
|
|||||||
<data name="EnvelopeArchived" xml:space="preserve">
|
<data name="EnvelopeArchived" xml:space="preserve">
|
||||||
<value>Archived</value>
|
<value>Archived</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="EnvelopeCompletelyConfirmed" xml:space="preserve">
|
||||||
|
<value>Completely confirmed</value>
|
||||||
|
</data>
|
||||||
<data name="EnvelopeCompletelySigned" xml:space="preserve">
|
<data name="EnvelopeCompletelySigned" xml:space="preserve">
|
||||||
<value>Completely signed</value>
|
<value>Completely signed</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -168,8 +186,11 @@
|
|||||||
<data name="EnvelopeDeleted" xml:space="preserve">
|
<data name="EnvelopeDeleted" xml:space="preserve">
|
||||||
<value>Envelope Deleted</value>
|
<value>Envelope Deleted</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="EnvelopePartlyConfirmed" xml:space="preserve">
|
||||||
|
<value>Partially confirmed</value>
|
||||||
|
</data>
|
||||||
<data name="EnvelopePartlySigned" xml:space="preserve">
|
<data name="EnvelopePartlySigned" xml:space="preserve">
|
||||||
<value>Partly signed</value>
|
<value>Partially signed</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="EnvelopeQueued" xml:space="preserve">
|
<data name="EnvelopeQueued" xml:space="preserve">
|
||||||
<value>Envelope Queued</value>
|
<value>Envelope Queued</value>
|
||||||
@@ -180,6 +201,9 @@
|
|||||||
<data name="EnvelopeReportCreated" xml:space="preserve">
|
<data name="EnvelopeReportCreated" xml:space="preserve">
|
||||||
<value>Signature certificate created</value>
|
<value>Signature certificate created</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="EnvelopeReportCreatedRaC" xml:space="preserve">
|
||||||
|
<value>Read confirmartion certificate created</value>
|
||||||
|
</data>
|
||||||
<data name="EnvelopeSaved" xml:space="preserve">
|
<data name="EnvelopeSaved" xml:space="preserve">
|
||||||
<value>Saved</value>
|
<value>Saved</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -196,7 +220,7 @@
|
|||||||
<value>Final email sent</value>
|
<value>Final email sent</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MessageConfirmationSent" xml:space="preserve">
|
<data name="MessageConfirmationSent" xml:space="preserve">
|
||||||
<value>Confirmation Sent</value>
|
<value>Finalization Confirmation Sent</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MessageDeletionSent" xml:space="preserve">
|
<data name="MessageDeletionSent" xml:space="preserve">
|
||||||
<value>Deletion Notice Sent</value>
|
<value>Deletion Notice Sent</value>
|
||||||
@@ -207,6 +231,12 @@
|
|||||||
<data name="No" xml:space="preserve">
|
<data name="No" xml:space="preserve">
|
||||||
<value>No</value>
|
<value>No</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PartlyConfirmed" xml:space="preserve">
|
||||||
|
<value>Partially confirmed</value>
|
||||||
|
</data>
|
||||||
|
<data name="PartlySigned" xml:space="preserve">
|
||||||
|
<value>Partially signed</value>
|
||||||
|
</data>
|
||||||
<data name="QualifiedSignature" xml:space="preserve">
|
<data name="QualifiedSignature" xml:space="preserve">
|
||||||
<value>Qualified Signature</value>
|
<value>Qualified Signature</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -226,7 +256,7 @@
|
|||||||
<value>Signature</value>
|
<value>Signature</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SignatureConfirmed" xml:space="preserve">
|
<data name="SignatureConfirmed" xml:space="preserve">
|
||||||
<value>Signature confirmed</value>
|
<value>Finalization confirmed</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Signed" xml:space="preserve">
|
<data name="Signed" xml:space="preserve">
|
||||||
<value>Signed</value>
|
<value>Signed</value>
|
||||||
@@ -234,6 +264,9 @@
|
|||||||
<data name="Success_FormClose" xml:space="preserve">
|
<data name="Success_FormClose" xml:space="preserve">
|
||||||
<value>Successful! Dialog is closed.successful! Dialog is closed.</value>
|
<value>Successful! Dialog is closed.successful! Dialog is closed.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Unconfirmed" xml:space="preserve">
|
||||||
|
<value>Unconfirmed</value>
|
||||||
|
</data>
|
||||||
<data name="Unsigned" xml:space="preserve">
|
<data name="Unsigned" xml:space="preserve">
|
||||||
<value>Unsigned</value>
|
<value>Unsigned</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -132,8 +132,17 @@
|
|||||||
<data name="Completed" xml:space="preserve">
|
<data name="Completed" xml:space="preserve">
|
||||||
<value>Abgeschlossen</value>
|
<value>Abgeschlossen</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="CompletelyConfirmed" xml:space="preserve">
|
||||||
|
<value>Vollständig bestätigt</value>
|
||||||
|
</data>
|
||||||
<data name="CompletelySigned" xml:space="preserve">
|
<data name="CompletelySigned" xml:space="preserve">
|
||||||
<value>Vollständig Signiert</value>
|
<value>Vollständig signiert</value>
|
||||||
|
</data>
|
||||||
|
<data name="Confirmation" xml:space="preserve">
|
||||||
|
<value>Lesebestätigung</value>
|
||||||
|
</data>
|
||||||
|
<data name="Confirmed" xml:space="preserve">
|
||||||
|
<value>Gelesen und bestätigt</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Contract" xml:space="preserve">
|
<data name="Contract" xml:space="preserve">
|
||||||
<value>Vertrag</value>
|
<value>Vertrag</value>
|
||||||
@@ -141,6 +150,9 @@
|
|||||||
<data name="Created" xml:space="preserve">
|
<data name="Created" xml:space="preserve">
|
||||||
<value>Erstellt</value>
|
<value>Erstellt</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DocumentConfirmed" xml:space="preserve">
|
||||||
|
<value>Dokument gelesen und bestätigt</value>
|
||||||
|
</data>
|
||||||
<data name="DocumentMod_Rotation" xml:space="preserve">
|
<data name="DocumentMod_Rotation" xml:space="preserve">
|
||||||
<value>Dokument Rotation geändert</value>
|
<value>Dokument Rotation geändert</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -150,6 +162,9 @@
|
|||||||
<data name="DocumentRejected" xml:space="preserve">
|
<data name="DocumentRejected" xml:space="preserve">
|
||||||
<value>Unterzeichnung abgelehnt</value>
|
<value>Unterzeichnung abgelehnt</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DocumentRejectedRaC" xml:space="preserve">
|
||||||
|
<value>Lesebestätigung abgelehnt</value>
|
||||||
|
</data>
|
||||||
<data name="DocumentSigned" xml:space="preserve">
|
<data name="DocumentSigned" xml:space="preserve">
|
||||||
<value>Dokument unterzeichnet</value>
|
<value>Dokument unterzeichnet</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -159,6 +174,9 @@
|
|||||||
<data name="EnvelopeArchived" xml:space="preserve">
|
<data name="EnvelopeArchived" xml:space="preserve">
|
||||||
<value>Archiviert</value>
|
<value>Archiviert</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="EnvelopeCompletelyConfirmed" xml:space="preserve">
|
||||||
|
<value>Vollständig gelesen und bestätigt</value>
|
||||||
|
</data>
|
||||||
<data name="EnvelopeCompletelySigned" xml:space="preserve">
|
<data name="EnvelopeCompletelySigned" xml:space="preserve">
|
||||||
<value>Vollständig signiert</value>
|
<value>Vollständig signiert</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -168,6 +186,9 @@
|
|||||||
<data name="EnvelopeDeleted" xml:space="preserve">
|
<data name="EnvelopeDeleted" xml:space="preserve">
|
||||||
<value>Umschlag Gelöscht</value>
|
<value>Umschlag Gelöscht</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="EnvelopePartlyConfirmed" xml:space="preserve">
|
||||||
|
<value>Teil-Bestätigt</value>
|
||||||
|
</data>
|
||||||
<data name="EnvelopePartlySigned" xml:space="preserve">
|
<data name="EnvelopePartlySigned" xml:space="preserve">
|
||||||
<value>Teil-Signiert</value>
|
<value>Teil-Signiert</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -180,6 +201,9 @@
|
|||||||
<data name="EnvelopeReportCreated" xml:space="preserve">
|
<data name="EnvelopeReportCreated" xml:space="preserve">
|
||||||
<value>Signierungszertifikat erstellt</value>
|
<value>Signierungszertifikat erstellt</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="EnvelopeReportCreatedRaC" xml:space="preserve">
|
||||||
|
<value>Lesebestätigungszertifikat erstellt</value>
|
||||||
|
</data>
|
||||||
<data name="EnvelopeSaved" xml:space="preserve">
|
<data name="EnvelopeSaved" xml:space="preserve">
|
||||||
<value>Gespeichert</value>
|
<value>Gespeichert</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -196,7 +220,7 @@
|
|||||||
<value>Abschlussemail versendet</value>
|
<value>Abschlussemail versendet</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MessageConfirmationSent" xml:space="preserve">
|
<data name="MessageConfirmationSent" xml:space="preserve">
|
||||||
<value>Signaturbestätigung versendet</value>
|
<value>Abschlussbestätigung versendet</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MessageDeletionSent" xml:space="preserve">
|
<data name="MessageDeletionSent" xml:space="preserve">
|
||||||
<value>Löschinformation versendet</value>
|
<value>Löschinformation versendet</value>
|
||||||
@@ -207,6 +231,9 @@
|
|||||||
<data name="No" xml:space="preserve">
|
<data name="No" xml:space="preserve">
|
||||||
<value>Nein</value>
|
<value>Nein</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PartlyConfirmed" xml:space="preserve">
|
||||||
|
<value>Teil-Bestätigt</value>
|
||||||
|
</data>
|
||||||
<data name="PartlySigned" xml:space="preserve">
|
<data name="PartlySigned" xml:space="preserve">
|
||||||
<value>Teil-Signiert</value>
|
<value>Teil-Signiert</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -214,7 +241,7 @@
|
|||||||
<value>Qualifizierte Signatur</value>
|
<value>Qualifizierte Signatur</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ReadAndSign" xml:space="preserve">
|
<data name="ReadAndSign" xml:space="preserve">
|
||||||
<value>Arbeitsanweisung</value>
|
<value>Lesebestätigung</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ResetTOTPUser" xml:space="preserve">
|
<data name="ResetTOTPUser" xml:space="preserve">
|
||||||
<value>Wollen Sie die 2-Faktor Definition für diesen Empfänger zurücksetzen. Der Empfänger muss sich dann neu identifizieren!</value>
|
<value>Wollen Sie die 2-Faktor Definition für diesen Empfänger zurücksetzen. Der Empfänger muss sich dann neu identifizieren!</value>
|
||||||
@@ -229,7 +256,7 @@
|
|||||||
<value>Signatur</value>
|
<value>Signatur</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SignatureConfirmed" xml:space="preserve">
|
<data name="SignatureConfirmed" xml:space="preserve">
|
||||||
<value>Signatur bestätigt</value>
|
<value>Abschluss bestätigt</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Signed" xml:space="preserve">
|
<data name="Signed" xml:space="preserve">
|
||||||
<value>Signiert</value>
|
<value>Signiert</value>
|
||||||
@@ -237,6 +264,9 @@
|
|||||||
<data name="Success_FormClose" xml:space="preserve">
|
<data name="Success_FormClose" xml:space="preserve">
|
||||||
<value>Erfolgreich! Dialog wird geschlossen.</value>
|
<value>Erfolgreich! Dialog wird geschlossen.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Unconfirmed" xml:space="preserve">
|
||||||
|
<value>Unbestätigt</value>
|
||||||
|
</data>
|
||||||
<data name="Unsigned" xml:space="preserve">
|
<data name="Unsigned" xml:space="preserve">
|
||||||
<value>Unsigniert</value>
|
<value>Unsigniert</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.6.0" />
|
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.6.0" />
|
||||||
<PackageReference Include="DigitalData.Core.Infrastructure" Version="2.6.1" />
|
<PackageReference Include="DigitalData.Core.Infrastructure" Version="2.6.1" />
|
||||||
<PackageReference Include="HtmlSanitizer" Version="9.0.892" />
|
<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="Microsoft.Identity.Client" Version="4.82.1" />
|
||||||
<PackageReference Include="QuestPDF" Version="2025.7.1" />
|
<PackageReference Include="QuestPDF" Version="2025.7.1" />
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.6.2" />
|
|
||||||
<PackageReference Include="HtmlSanitizer" Version="9.0.892" />
|
|
||||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.82.1" />
|
|
||||||
<PackageReference Include="Quartz" Version="3.9.0" />
|
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
|
|
||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.4" />
|
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
|
|
||||||
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
|
|
||||||
<ProjectReference Include="..\EnvelopeGenerator.PdfEditor\EnvelopeGenerator.PdfEditor.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using EnvelopeGenerator.Domain.Constants;
|
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
|
||||||
using Quartz;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Jobs.APIBackendJobs;
|
|
||||||
|
|
||||||
public class APIEnvelopeJob(ILogger<APIEnvelopeJob>? logger = null) : IJob
|
|
||||||
{
|
|
||||||
private readonly ILogger<APIEnvelopeJob> _logger = logger ?? NullLogger<APIEnvelopeJob>.Instance;
|
|
||||||
|
|
||||||
public async Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
|
||||||
var jobId = context.JobDetail.Key.ToString();
|
|
||||||
_logger.LogDebug("API Envelopes - Starting job {JobId}", jobId);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var connectionString = context.MergedJobDataMap.GetString(Value.DATABASE);
|
|
||||||
if (string.IsNullOrWhiteSpace(connectionString))
|
|
||||||
{
|
|
||||||
_logger.LogWarning("API Envelopes - Connection string missing");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await using var connection = new SqlConnection(connectionString);
|
|
||||||
await connection.OpenAsync(context.CancellationToken);
|
|
||||||
|
|
||||||
await ProcessInvitationsAsync(connection, context.CancellationToken);
|
|
||||||
await ProcessWithdrawnAsync(connection, context.CancellationToken);
|
|
||||||
|
|
||||||
_logger.LogDebug("API Envelopes - Completed job {JobId} successfully", jobId);
|
|
||||||
}
|
|
||||||
catch (System.Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "API Envelopes job failed");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_logger.LogDebug("API Envelopes execution for {JobId} ended", jobId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ProcessInvitationsAsync(SqlConnection connection, System.Threading.CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
const string sql = "SELECT GUID FROM TBSIG_ENVELOPE WHERE SOURCE = 'API' AND STATUS = 1003 ORDER BY GUID";
|
|
||||||
var envelopeIds = new List<int>();
|
|
||||||
|
|
||||||
await using (var command = new SqlCommand(sql, connection))
|
|
||||||
await using (var reader = await command.ExecuteReaderAsync(cancellationToken))
|
|
||||||
{
|
|
||||||
while (await reader.ReadAsync(cancellationToken))
|
|
||||||
{
|
|
||||||
if (reader[0] is int id)
|
|
||||||
{
|
|
||||||
envelopeIds.Add(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (envelopeIds.Count == 0)
|
|
||||||
{
|
|
||||||
_logger.LogDebug("SendInvMail - No envelopes found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("SendInvMail - Found {Count} envelopes", envelopeIds.Count);
|
|
||||||
var total = envelopeIds.Count;
|
|
||||||
var current = 1;
|
|
||||||
|
|
||||||
foreach (var id in envelopeIds)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("SendInvMail - Processing Envelope {EnvelopeId} ({Current}/{Total})", id, current, total);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Placeholder for invitation email sending logic.
|
|
||||||
_logger.LogDebug("SendInvMail - Marking envelope {EnvelopeId} as queued", id);
|
|
||||||
const string updateSql = "UPDATE TBSIG_ENVELOPE SET CURRENT_WORK_APP = @App WHERE GUID = @Id";
|
|
||||||
await using var updateCommand = new SqlCommand(updateSql, connection);
|
|
||||||
updateCommand.Parameters.AddWithValue("@App", "signFLOW_API_EnvJob_InvMail");
|
|
||||||
updateCommand.Parameters.AddWithValue("@Id", id);
|
|
||||||
await updateCommand.ExecuteNonQueryAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
catch (System.Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "SendInvMail - Unhandled exception while working envelope {EnvelopeId}", id);
|
|
||||||
}
|
|
||||||
|
|
||||||
current++;
|
|
||||||
_logger.LogInformation("SendInvMail - Envelope finalized");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task ProcessWithdrawnAsync(SqlConnection connection, System.Threading.CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
const string sql = @"SELECT ENV.GUID, REJ.COMMENT AS REJECTION_REASON FROM
|
|
||||||
(SELECT * FROM TBSIG_ENVELOPE WHERE STATUS = 1009 AND SOURCE = 'API') ENV INNER JOIN
|
|
||||||
(SELECT MAX(GUID) GUID, ENVELOPE_ID, MAX(ADDED_WHEN) ADDED_WHEN, MAX(ACTION_DATE) ACTION_DATE, COMMENT FROM TBSIG_ENVELOPE_HISTORY WHERE STATUS = 1009 GROUP BY ENVELOPE_ID, COMMENT ) REJ ON ENV.GUID = REJ.ENVELOPE_ID LEFT JOIN
|
|
||||||
(SELECT * FROM TBSIG_ENVELOPE_HISTORY WHERE STATUS = 3004 ) M_Send ON ENV.GUID = M_Send.ENVELOPE_ID
|
|
||||||
WHERE M_Send.GUID IS NULL";
|
|
||||||
|
|
||||||
var withdrawn = new List<(int EnvelopeId, string Reason)>();
|
|
||||||
await using (var command = new SqlCommand(sql, connection))
|
|
||||||
await using (var reader = await command.ExecuteReaderAsync(cancellationToken))
|
|
||||||
{
|
|
||||||
while (await reader.ReadAsync(cancellationToken))
|
|
||||||
{
|
|
||||||
var id = reader.GetInt32(0);
|
|
||||||
var reason = reader.IsDBNull(1) ? string.Empty : reader.GetString(1);
|
|
||||||
withdrawn.Add((id, reason));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (withdrawn.Count == 0)
|
|
||||||
{
|
|
||||||
_logger.LogDebug("WithdrawnEnv - No envelopes found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("WithdrawnEnv - Found {Count} envelopes", withdrawn.Count);
|
|
||||||
var total = withdrawn.Count;
|
|
||||||
var current = 1;
|
|
||||||
|
|
||||||
foreach (var (envelopeId, reason) in withdrawn)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("WithdrawnEnv - Processing Envelope {EnvelopeId} ({Current}/{Total})", envelopeId, current, total);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Log withdrawn mail trigger placeholder
|
|
||||||
const string insertHistory = "INSERT INTO TBSIG_ENVELOPE_HISTORY (ENVELOPE_ID, STATUS, USER_REFERENCE, ADDED_WHEN, ACTION_DATE, COMMENT) VALUES (@EnvelopeId, @Status, @UserReference, GETDATE(), GETDATE(), @Comment)";
|
|
||||||
await using var insertCommand = new SqlCommand(insertHistory, connection);
|
|
||||||
insertCommand.Parameters.AddWithValue("@EnvelopeId", envelopeId);
|
|
||||||
insertCommand.Parameters.AddWithValue("@Status", 3004);
|
|
||||||
insertCommand.Parameters.AddWithValue("@UserReference", "API");
|
|
||||||
insertCommand.Parameters.AddWithValue("@Comment", reason ?? string.Empty);
|
|
||||||
await insertCommand.ExecuteNonQueryAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
catch (System.Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "WithdrawnEnv - Unhandled exception while working envelope {EnvelopeId}", envelopeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
current++;
|
|
||||||
_logger.LogInformation("WithdrawnEnv - Envelope finalized");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Data;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Jobs;
|
|
||||||
|
|
||||||
public static class DataRowExtensions
|
|
||||||
{
|
|
||||||
public static T? GetValueOrDefault<T>(this DataRow row, string columnName, T? defaultValue = default)
|
|
||||||
{
|
|
||||||
if (!row.Table.Columns.Contains(columnName))
|
|
||||||
{
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var value = row[columnName];
|
|
||||||
if (value == DBNull.Value)
|
|
||||||
{
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return (T)Convert.ChangeType(value, typeof(T));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.Jobs.FinalizeDocument;
|
|
||||||
|
|
||||||
public static class FinalizeDocumentExceptions
|
|
||||||
{
|
|
||||||
public class MergeDocumentException : ApplicationException
|
|
||||||
{
|
|
||||||
public MergeDocumentException(string message) : base(message) { }
|
|
||||||
public MergeDocumentException(string message, Exception innerException) : base(message, innerException) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BurnAnnotationException : ApplicationException
|
|
||||||
{
|
|
||||||
public BurnAnnotationException(string message) : base(message) { }
|
|
||||||
public BurnAnnotationException(string message, Exception innerException) : base(message, innerException) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CreateReportException : ApplicationException
|
|
||||||
{
|
|
||||||
public CreateReportException(string message) : base(message) { }
|
|
||||||
public CreateReportException(string message, Exception innerException) : base(message, innerException) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ExportDocumentException : ApplicationException
|
|
||||||
{
|
|
||||||
public ExportDocumentException(string message) : base(message) { }
|
|
||||||
public ExportDocumentException(string message, Exception innerException) : base(message, innerException) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,229 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Data;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using EnvelopeGenerator.Domain.Constants;
|
|
||||||
using EnvelopeGenerator.Domain.Entities;
|
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Quartz;
|
|
||||||
using static EnvelopeGenerator.Jobs.FinalizeDocument.FinalizeDocumentExceptions;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Jobs.FinalizeDocument;
|
|
||||||
|
|
||||||
public class FinalizeDocumentJob : IJob
|
|
||||||
{
|
|
||||||
private readonly ILogger<FinalizeDocumentJob> _logger;
|
|
||||||
private readonly PDFBurner _pdfBurner;
|
|
||||||
private readonly PDFMerger _pdfMerger;
|
|
||||||
private readonly ReportCreator _reportCreator;
|
|
||||||
|
|
||||||
private record ConfigSettings(string DocumentPath, string DocumentPathOrigin, string ExportPath);
|
|
||||||
|
|
||||||
public FinalizeDocumentJob(
|
|
||||||
ILogger<FinalizeDocumentJob> logger,
|
|
||||||
PDFBurner pdfBurner,
|
|
||||||
PDFMerger pdfMerger,
|
|
||||||
ReportCreator reportCreator)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_pdfBurner = pdfBurner;
|
|
||||||
_pdfMerger = pdfMerger;
|
|
||||||
_reportCreator = reportCreator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Execute(IJobExecutionContext context)
|
|
||||||
{
|
|
||||||
var jobId = context.JobDetail.Key.ToString();
|
|
||||||
_logger.LogDebug("Starting job {JobId}", jobId);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var connectionString = context.MergedJobDataMap.GetString(Value.DATABASE);
|
|
||||||
if (string.IsNullOrWhiteSpace(connectionString))
|
|
||||||
{
|
|
||||||
_logger.LogWarning("FinalizeDocument - Connection string missing");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await using var connection = new SqlConnection(connectionString);
|
|
||||||
await connection.OpenAsync(context.CancellationToken);
|
|
||||||
|
|
||||||
var config = await LoadConfigurationAsync(connection, context.CancellationToken);
|
|
||||||
var envelopes = await LoadCompletedEnvelopesAsync(connection, context.CancellationToken);
|
|
||||||
|
|
||||||
if (envelopes.Count == 0)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("No completed envelopes found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var total = envelopes.Count;
|
|
||||||
var current = 1;
|
|
||||||
|
|
||||||
foreach (var envelopeId in envelopes)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Finalizing Envelope {EnvelopeId} ({Current}/{Total})", envelopeId, current, total);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var envelopeData = await GetEnvelopeDataAsync(connection, envelopeId, context.CancellationToken);
|
|
||||||
if (envelopeData is null)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Envelope data not found for {EnvelopeId}", envelopeId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = envelopeData.Value;
|
|
||||||
|
|
||||||
var envelope = new Envelope
|
|
||||||
{
|
|
||||||
Id = envelopeId,
|
|
||||||
Uuid = data.EnvelopeUuid ?? string.Empty,
|
|
||||||
Title = data.Title ?? string.Empty,
|
|
||||||
FinalEmailToCreator = (int)FinalEmailType.No,
|
|
||||||
FinalEmailToReceivers = (int)FinalEmailType.No
|
|
||||||
};
|
|
||||||
|
|
||||||
var burned = _pdfBurner.BurnAnnotsToPDF(data.DocumentBytes, data.AnnotationData, envelopeId);
|
|
||||||
var report = _reportCreator.CreateReport(connection, envelope);
|
|
||||||
var merged = _pdfMerger.MergeDocuments(burned, report);
|
|
||||||
|
|
||||||
var outputDirectory = Path.Combine(config.ExportPath, data.ParentFolderUid);
|
|
||||||
Directory.CreateDirectory(outputDirectory);
|
|
||||||
var outputPath = Path.Combine(outputDirectory, $"{envelope.Uuid}.pdf");
|
|
||||||
await File.WriteAllBytesAsync(outputPath, merged, context.CancellationToken);
|
|
||||||
|
|
||||||
await UpdateDocumentResultAsync(connection, envelopeId, merged, context.CancellationToken);
|
|
||||||
await ArchiveEnvelopeAsync(connection, envelopeId, context.CancellationToken);
|
|
||||||
}
|
|
||||||
catch (MergeDocumentException ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "Certificate Document job failed at merging documents");
|
|
||||||
}
|
|
||||||
catch (ExportDocumentException ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "Certificate Document job failed at exporting document");
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Unhandled exception while working envelope {EnvelopeId}", envelopeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
current++;
|
|
||||||
_logger.LogInformation("Envelope {EnvelopeId} finalized", envelopeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogDebug("Completed job {JobId} successfully", jobId);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Certificate Document job failed");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Job execution for {JobId} ended", jobId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<ConfigSettings> LoadConfigurationAsync(SqlConnection connection, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
const string sql = "SELECT TOP 1 DOCUMENT_PATH, EXPORT_PATH FROM TBSIG_CONFIG";
|
|
||||||
await using var command = new SqlCommand(sql, connection);
|
|
||||||
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
|
||||||
if (await reader.ReadAsync(cancellationToken))
|
|
||||||
{
|
|
||||||
var documentPath = reader.IsDBNull(0) ? string.Empty : reader.GetString(0);
|
|
||||||
var exportPath = reader.IsDBNull(1) ? string.Empty : reader.GetString(1);
|
|
||||||
return new ConfigSettings(documentPath, documentPath, exportPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ConfigSettings(string.Empty, string.Empty, Path.GetTempPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<List<int>> LoadCompletedEnvelopesAsync(SqlConnection connection, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
const string sql = "SELECT GUID FROM TBSIG_ENVELOPE WHERE STATUS = @Status AND DATEDIFF(minute, CHANGED_WHEN, GETDATE()) >= 1 ORDER BY GUID";
|
|
||||||
var ids = new List<int>();
|
|
||||||
await using var command = new SqlCommand(sql, connection);
|
|
||||||
command.Parameters.AddWithValue("@Status", (int)EnvelopeStatus.EnvelopeCompletelySigned);
|
|
||||||
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
|
||||||
while (await reader.ReadAsync(cancellationToken))
|
|
||||||
{
|
|
||||||
ids.Add(reader.GetInt32(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<(int EnvelopeId, string? EnvelopeUuid, string? Title, byte[] DocumentBytes, List<string> AnnotationData, string ParentFolderUid)?> GetEnvelopeDataAsync(SqlConnection connection, int envelopeId, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
const string sql = @"SELECT T.GUID, T.ENVELOPE_UUID, T.TITLE, T2.FILEPATH, T2.BYTE_DATA FROM [dbo].[TBSIG_ENVELOPE] T
|
|
||||||
JOIN TBSIG_ENVELOPE_DOCUMENT T2 ON T.GUID = T2.ENVELOPE_ID
|
|
||||||
WHERE T.GUID = @EnvelopeId";
|
|
||||||
|
|
||||||
await using var command = new SqlCommand(sql, connection);
|
|
||||||
command.Parameters.AddWithValue("@EnvelopeId", envelopeId);
|
|
||||||
await using var reader = await command.ExecuteReaderAsync(CommandBehavior.SingleRow, cancellationToken);
|
|
||||||
if (!await reader.ReadAsync(cancellationToken))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var envelopeUuid = reader.IsDBNull(1) ? string.Empty : reader.GetString(1);
|
|
||||||
var title = reader.IsDBNull(2) ? string.Empty : reader.GetString(2);
|
|
||||||
var filePath = reader.IsDBNull(3) ? string.Empty : reader.GetString(3);
|
|
||||||
var bytes = reader.IsDBNull(4) ? Array.Empty<byte>() : (byte[])reader[4];
|
|
||||||
await reader.CloseAsync();
|
|
||||||
|
|
||||||
if (bytes.Length == 0 && !string.IsNullOrWhiteSpace(filePath) && File.Exists(filePath))
|
|
||||||
{
|
|
||||||
bytes = await File.ReadAllBytesAsync(filePath, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
var annotations = await GetAnnotationDataAsync(connection, envelopeId, cancellationToken);
|
|
||||||
|
|
||||||
var parentFolderUid = !string.IsNullOrWhiteSpace(filePath)
|
|
||||||
? Path.GetFileName(Path.GetDirectoryName(filePath) ?? string.Empty)
|
|
||||||
: envelopeUuid;
|
|
||||||
|
|
||||||
return (envelopeId, envelopeUuid, title, bytes, annotations, parentFolderUid ?? envelopeUuid ?? envelopeId.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<List<string>> GetAnnotationDataAsync(SqlConnection connection, int envelopeId, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
const string sql = "SELECT VALUE FROM TBSIG_DOCUMENT_STATUS WHERE ENVELOPE_ID = @EnvelopeId";
|
|
||||||
var result = new List<string>();
|
|
||||||
await using var command = new SqlCommand(sql, connection);
|
|
||||||
command.Parameters.AddWithValue("@EnvelopeId", envelopeId);
|
|
||||||
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
|
||||||
while (await reader.ReadAsync(cancellationToken))
|
|
||||||
{
|
|
||||||
if (!reader.IsDBNull(0))
|
|
||||||
{
|
|
||||||
result.Add(reader.GetString(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task UpdateDocumentResultAsync(SqlConnection connection, int envelopeId, byte[] bytes, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
const string sql = "UPDATE TBSIG_ENVELOPE SET DOC_RESULT = @ImageData WHERE GUID = @EnvelopeId";
|
|
||||||
await using var command = new SqlCommand(sql, connection);
|
|
||||||
command.Parameters.AddWithValue("@ImageData", bytes);
|
|
||||||
command.Parameters.AddWithValue("@EnvelopeId", envelopeId);
|
|
||||||
await command.ExecuteNonQueryAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task ArchiveEnvelopeAsync(SqlConnection connection, int envelopeId, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
const string sql = "UPDATE TBSIG_ENVELOPE SET STATUS = @Status, CHANGED_WHEN = GETDATE() WHERE GUID = @EnvelopeId";
|
|
||||||
await using var command = new SqlCommand(sql, connection);
|
|
||||||
command.Parameters.AddWithValue("@Status", (int)EnvelopeStatus.EnvelopeArchived);
|
|
||||||
command.Parameters.AddWithValue("@EnvelopeId", envelopeId);
|
|
||||||
await command.ExecuteNonQueryAsync(cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,277 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using iText.IO.Image;
|
|
||||||
using iText.Kernel.Colors;
|
|
||||||
using iText.Kernel.Pdf;
|
|
||||||
using iText.Kernel.Pdf.Canvas;
|
|
||||||
using iText.Layout;
|
|
||||||
using iText.Layout.Element;
|
|
||||||
using iText.Layout.Font;
|
|
||||||
using iText.Layout.Properties;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using static EnvelopeGenerator.Jobs.FinalizeDocument.FinalizeDocumentExceptions;
|
|
||||||
using LayoutImage = iText.Layout.Element.Image;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Jobs.FinalizeDocument;
|
|
||||||
|
|
||||||
public class PDFBurner
|
|
||||||
{
|
|
||||||
private static readonly FontProvider FontProvider = CreateFontProvider();
|
|
||||||
private readonly ILogger<PDFBurner> _logger;
|
|
||||||
private readonly PDFBurnerParams _pdfBurnerParams;
|
|
||||||
|
|
||||||
public PDFBurner() : this(NullLogger<PDFBurner>.Instance, new PDFBurnerParams())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public PDFBurner(ILogger<PDFBurner> logger, PDFBurnerParams? pdfBurnerParams = null)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_pdfBurnerParams = pdfBurnerParams ?? new PDFBurnerParams();
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] BurnAnnotsToPDF(byte[] sourceBuffer, IList<string> instantJsonList, int envelopeId)
|
|
||||||
{
|
|
||||||
if (sourceBuffer is null || sourceBuffer.Length == 0)
|
|
||||||
{
|
|
||||||
throw new BurnAnnotationException("Source document is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var inputStream = new MemoryStream(sourceBuffer);
|
|
||||||
using var outputStream = new MemoryStream();
|
|
||||||
using var reader = new PdfReader(inputStream);
|
|
||||||
using var writer = new PdfWriter(outputStream);
|
|
||||||
using var pdf = new PdfDocument(reader, writer);
|
|
||||||
|
|
||||||
foreach (var json in instantJsonList ?? Enumerable.Empty<string>())
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(json))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var annotationData = JsonConvert.DeserializeObject<AnnotationData>(json);
|
|
||||||
if (annotationData?.annotations is null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
annotationData.annotations.Reverse();
|
|
||||||
|
|
||||||
foreach (var annotation in annotationData.annotations)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
switch (annotation.type)
|
|
||||||
{
|
|
||||||
case AnnotationType.Image:
|
|
||||||
AddImageAnnotation(pdf, annotation, annotationData.attachments);
|
|
||||||
break;
|
|
||||||
case AnnotationType.Ink:
|
|
||||||
AddInkAnnotation(pdf, annotation);
|
|
||||||
break;
|
|
||||||
case AnnotationType.Widget:
|
|
||||||
var formFieldValue = annotationData.formFieldValues?.FirstOrDefault(fv => fv.name == annotation.id);
|
|
||||||
if (formFieldValue is not null && !_pdfBurnerParams.IgnoredLabels.Contains(formFieldValue.value))
|
|
||||||
{
|
|
||||||
AddFormFieldValue(pdf, annotation, formFieldValue.value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "Error applying annotation {AnnotationId} on envelope {EnvelopeId}", annotation.id, envelopeId);
|
|
||||||
throw new BurnAnnotationException("Adding annotation failed", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pdf.Close();
|
|
||||||
return outputStream.ToArray();
|
|
||||||
}
|
|
||||||
catch (BurnAnnotationException)
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Failed to burn annotations for envelope {EnvelopeId}", envelopeId);
|
|
||||||
throw new BurnAnnotationException("Annotations could not be burned", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddImageAnnotation(PdfDocument pdf, Annotation annotation, Dictionary<string, Attachment>? attachments)
|
|
||||||
{
|
|
||||||
if (attachments is null || string.IsNullOrWhiteSpace(annotation.imageAttachmentId) || !attachments.TryGetValue(annotation.imageAttachmentId, out var attachment))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var page = pdf.GetPage(annotation.pageIndex + 1);
|
|
||||||
var bounds = annotation.bbox.Select(ToInches).ToList();
|
|
||||||
var x = (float)bounds[0];
|
|
||||||
var y = (float)bounds[1];
|
|
||||||
var width = (float)bounds[2];
|
|
||||||
var height = (float)bounds[3];
|
|
||||||
|
|
||||||
var imageBytes = Convert.FromBase64String(attachment.binary);
|
|
||||||
var imageData = ImageDataFactory.Create(imageBytes);
|
|
||||||
var image = new LayoutImage(imageData)
|
|
||||||
.ScaleAbsolute(width, height)
|
|
||||||
.SetFixedPosition(annotation.pageIndex + 1, x, y);
|
|
||||||
|
|
||||||
using var canvas = new Canvas(new PdfCanvas(page), page.GetPageSize());
|
|
||||||
canvas.Add(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddInkAnnotation(PdfDocument pdf, Annotation annotation)
|
|
||||||
{
|
|
||||||
if (annotation.lines?.points is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var page = pdf.GetPage(annotation.pageIndex + 1);
|
|
||||||
var canvas = new PdfCanvas(page);
|
|
||||||
var color = ParseColor(annotation.strokeColor);
|
|
||||||
canvas.SetStrokeColor(color);
|
|
||||||
canvas.SetLineWidth(1);
|
|
||||||
|
|
||||||
foreach (var segment in annotation.lines.points)
|
|
||||||
{
|
|
||||||
var first = true;
|
|
||||||
foreach (var point in segment)
|
|
||||||
{
|
|
||||||
var (px, py) = (ToInches(point[0]), ToInches(point[1]));
|
|
||||||
if (first)
|
|
||||||
{
|
|
||||||
canvas.MoveTo(px, py);
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
canvas.LineTo(px, py);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
canvas.Stroke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static FontProvider CreateFontProvider()
|
|
||||||
{
|
|
||||||
var provider = new FontProvider();
|
|
||||||
provider.AddStandardPdfFonts();
|
|
||||||
provider.AddSystemFonts();
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddFormFieldValue(PdfDocument pdf, Annotation annotation, string value)
|
|
||||||
{
|
|
||||||
var bounds = annotation.bbox.Select(ToInches).ToList();
|
|
||||||
var x = (float)bounds[0];
|
|
||||||
var y = (float)bounds[1];
|
|
||||||
var width = (float)bounds[2];
|
|
||||||
var height = (float)bounds[3];
|
|
||||||
|
|
||||||
var page = pdf.GetPage(annotation.pageIndex + 1);
|
|
||||||
var canvas = new Canvas(new PdfCanvas(page), page.GetPageSize());
|
|
||||||
canvas.SetProperty(Property.FONT_PROVIDER, FontProvider);
|
|
||||||
canvas.SetProperty(Property.FONT, FontProvider.GetFontSet());
|
|
||||||
|
|
||||||
var paragraph = new Paragraph(value)
|
|
||||||
.SetFontSize(_pdfBurnerParams.FontSize)
|
|
||||||
.SetFontColor(ColorConstants.BLACK)
|
|
||||||
.SetFontFamily(_pdfBurnerParams.FontName);
|
|
||||||
|
|
||||||
if (_pdfBurnerParams.FontStyle.HasFlag(FontStyle.Italic))
|
|
||||||
{
|
|
||||||
paragraph.SetItalic();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_pdfBurnerParams.FontStyle.HasFlag(FontStyle.Bold))
|
|
||||||
{
|
|
||||||
paragraph.SetBold();
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.ShowTextAligned(
|
|
||||||
paragraph,
|
|
||||||
x + (float)_pdfBurnerParams.TopMargin,
|
|
||||||
y + (float)_pdfBurnerParams.YOffset,
|
|
||||||
annotation.pageIndex + 1,
|
|
||||||
iText.Layout.Properties.TextAlignment.LEFT,
|
|
||||||
iText.Layout.Properties.VerticalAlignment.TOP,
|
|
||||||
0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DeviceRgb ParseColor(string? color)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(color))
|
|
||||||
{
|
|
||||||
return new DeviceRgb(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var drawingColor = ColorTranslator.FromHtml(color);
|
|
||||||
return new DeviceRgb(drawingColor.R, drawingColor.G, drawingColor.B);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return new DeviceRgb(0, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static double ToInches(double value) => value / 72d;
|
|
||||||
private static double ToInches(float value) => value / 72d;
|
|
||||||
|
|
||||||
#region Model
|
|
||||||
private static class AnnotationType
|
|
||||||
{
|
|
||||||
public const string Image = "pspdfkit/image";
|
|
||||||
public const string Ink = "pspdfkit/ink";
|
|
||||||
public const string Widget = "pspdfkit/widget";
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class AnnotationData
|
|
||||||
{
|
|
||||||
public List<Annotation>? annotations { get; set; }
|
|
||||||
public Dictionary<string, Attachment>? attachments { get; set; }
|
|
||||||
public List<FormFieldValue>? formFieldValues { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class Annotation
|
|
||||||
{
|
|
||||||
public string id { get; set; } = string.Empty;
|
|
||||||
public List<double> bbox { get; set; } = new();
|
|
||||||
public string type { get; set; } = string.Empty;
|
|
||||||
public string imageAttachmentId { get; set; } = string.Empty;
|
|
||||||
public Lines? lines { get; set; }
|
|
||||||
public int pageIndex { get; set; }
|
|
||||||
public string strokeColor { get; set; } = string.Empty;
|
|
||||||
public string egName { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class Lines
|
|
||||||
{
|
|
||||||
public List<List<List<float>>> points { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class Attachment
|
|
||||||
{
|
|
||||||
public string binary { get; set; } = string.Empty;
|
|
||||||
public string contentType { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class FormFieldValue
|
|
||||||
{
|
|
||||||
public string name { get; set; } = string.Empty;
|
|
||||||
public string value { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Jobs.FinalizeDocument;
|
|
||||||
|
|
||||||
public class PDFBurnerParams
|
|
||||||
{
|
|
||||||
public List<string> IgnoredLabels { get; } = new() { "Date", "Datum", "ZIP", "PLZ", "Place", "Ort", "Position", "Stellung" };
|
|
||||||
|
|
||||||
public double TopMargin { get; set; } = 0.1;
|
|
||||||
|
|
||||||
public double YOffset { get; set; } = -0.3;
|
|
||||||
|
|
||||||
public string FontName { get; set; } = "Arial";
|
|
||||||
|
|
||||||
public int FontSize { get; set; } = 8;
|
|
||||||
|
|
||||||
public FontStyle FontStyle { get; set; } = FontStyle.Italic;
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
using System.IO;
|
|
||||||
using iText.Kernel.Pdf;
|
|
||||||
using iText.Kernel.Utils;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
|
||||||
using static EnvelopeGenerator.Jobs.FinalizeDocument.FinalizeDocumentExceptions;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Jobs.FinalizeDocument;
|
|
||||||
|
|
||||||
public class PDFMerger
|
|
||||||
{
|
|
||||||
private readonly ILogger<PDFMerger> _logger;
|
|
||||||
|
|
||||||
public PDFMerger() : this(NullLogger<PDFMerger>.Instance)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public PDFMerger(ILogger<PDFMerger> logger)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] MergeDocuments(byte[] document, byte[] report)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var finalStream = new MemoryStream();
|
|
||||||
using var documentReader = new PdfReader(new MemoryStream(document));
|
|
||||||
using var reportReader = new PdfReader(new MemoryStream(report));
|
|
||||||
using var writer = new PdfWriter(finalStream);
|
|
||||||
using var targetDoc = new PdfDocument(documentReader, writer);
|
|
||||||
using var reportDoc = new PdfDocument(reportReader);
|
|
||||||
|
|
||||||
var merger = new PdfMerger(targetDoc);
|
|
||||||
merger.Merge(reportDoc, 1, reportDoc.GetNumberOfPages());
|
|
||||||
|
|
||||||
targetDoc.Close();
|
|
||||||
return finalStream.ToArray();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Failed to merge PDF documents");
|
|
||||||
throw new MergeDocumentException("Documents could not be merged", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
using System.Data;
|
|
||||||
using System.IO;
|
|
||||||
using EnvelopeGenerator.Domain.Entities;
|
|
||||||
using iText.Kernel.Pdf;
|
|
||||||
using iText.Layout.Element;
|
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
|
||||||
using static EnvelopeGenerator.Jobs.FinalizeDocument.FinalizeDocumentExceptions;
|
|
||||||
using LayoutDocument = iText.Layout.Document;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Jobs.FinalizeDocument;
|
|
||||||
|
|
||||||
public class ReportCreator
|
|
||||||
{
|
|
||||||
private readonly ILogger<ReportCreator> _logger;
|
|
||||||
|
|
||||||
public ReportCreator() : this(NullLogger<ReportCreator>.Instance)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReportCreator(ILogger<ReportCreator> logger)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] CreateReport(SqlConnection connection, Envelope envelope)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var reportItems = LoadReportItems(connection, envelope.Id);
|
|
||||||
using var stream = new MemoryStream();
|
|
||||||
using var writer = new PdfWriter(stream);
|
|
||||||
using var pdf = new PdfDocument(writer);
|
|
||||||
using var document = new LayoutDocument(pdf);
|
|
||||||
|
|
||||||
document.Add(new Paragraph("Envelope Finalization Report").SetFontSize(16));
|
|
||||||
document.Add(new Paragraph($"Envelope Id: {envelope.Id}"));
|
|
||||||
document.Add(new Paragraph($"UUID: {envelope.Uuid}"));
|
|
||||||
document.Add(new Paragraph($"Title: {envelope.Title}"));
|
|
||||||
document.Add(new Paragraph($"Subject: {envelope.Comment}"));
|
|
||||||
document.Add(new Paragraph($"Generated: {DateTime.UtcNow:O}"));
|
|
||||||
document.Add(new Paragraph(" "));
|
|
||||||
|
|
||||||
var table = new Table(4).UseAllAvailableWidth();
|
|
||||||
table.AddHeaderCell("Date");
|
|
||||||
table.AddHeaderCell("Status");
|
|
||||||
table.AddHeaderCell("User");
|
|
||||||
table.AddHeaderCell("EnvelopeId");
|
|
||||||
|
|
||||||
foreach (var item in reportItems.OrderByDescending(r => r.ItemDate))
|
|
||||||
{
|
|
||||||
table.AddCell(item.ItemDate.ToString("u"));
|
|
||||||
table.AddCell(item.ItemStatus.ToString());
|
|
||||||
table.AddCell(item.ItemUserReference);
|
|
||||||
table.AddCell(item.EnvelopeId.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
document.Add(table);
|
|
||||||
document.Close();
|
|
||||||
return stream.ToArray();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Could not create report for envelope {EnvelopeId}", envelope.Id);
|
|
||||||
throw new CreateReportException("Could not prepare report data", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ReportItem> LoadReportItems(SqlConnection connection, int envelopeId)
|
|
||||||
{
|
|
||||||
const string sql = "SELECT ENVELOPE_ID, POS_WHEN, POS_STATUS, POS_WHO FROM VWSIG_ENVELOPE_REPORT WHERE ENVELOPE_ID = @EnvelopeId";
|
|
||||||
var result = new List<ReportItem>();
|
|
||||||
|
|
||||||
using var command = new SqlCommand(sql, connection);
|
|
||||||
command.Parameters.AddWithValue("@EnvelopeId", envelopeId);
|
|
||||||
using var reader = command.ExecuteReader();
|
|
||||||
while (reader.Read())
|
|
||||||
{
|
|
||||||
result.Add(new ReportItem
|
|
||||||
{
|
|
||||||
EnvelopeId = reader.GetInt32(0),
|
|
||||||
ItemDate = reader.IsDBNull(1) ? DateTime.MinValue : reader.GetDateTime(1),
|
|
||||||
ItemStatus = reader.IsDBNull(2) ? default : (EnvelopeGenerator.Domain.Constants.EnvelopeStatus)reader.GetInt32(2),
|
|
||||||
ItemUserReference = reader.IsDBNull(3) ? string.Empty : reader.GetString(3)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using EnvelopeGenerator.Domain.Constants;
|
|
||||||
using EnvelopeGenerator.Domain.Entities;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Jobs.FinalizeDocument;
|
|
||||||
|
|
||||||
public class ReportItem
|
|
||||||
{
|
|
||||||
public Envelope? Envelope { get; set; }
|
|
||||||
public int EnvelopeId { get; set; }
|
|
||||||
public string EnvelopeTitle { get; set; } = string.Empty;
|
|
||||||
public string EnvelopeSubject { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public EnvelopeStatus ItemStatus { get; set; }
|
|
||||||
|
|
||||||
public string ItemStatusTranslated => ItemStatus.ToString();
|
|
||||||
|
|
||||||
public string ItemUserReference { get; set; } = string.Empty;
|
|
||||||
public DateTime ItemDate { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Jobs.FinalizeDocument;
|
|
||||||
|
|
||||||
public class ReportSource
|
|
||||||
{
|
|
||||||
public List<ReportItem> Items { get; set; } = new();
|
|
||||||
}
|
|
||||||
@@ -105,17 +105,13 @@ namespace EnvelopeGenerator.PdfEditor
|
|||||||
public Pdf<TInputStream, TOutputStream> Background<TSignature>(IEnumerable<TSignature> signatures, double widthPx = 1.9500000000000002, double heightPx = 2.52)
|
public Pdf<TInputStream, TOutputStream> Background<TSignature>(IEnumerable<TSignature> signatures, double widthPx = 1.9500000000000002, double heightPx = 2.52)
|
||||||
where TSignature : ISignature
|
where TSignature : ISignature
|
||||||
{
|
{
|
||||||
// once per page
|
|
||||||
Page(page =>
|
|
||||||
{
|
|
||||||
var canvas = new PdfCanvas(page);
|
|
||||||
canvas.ConcatMatrix(1, 0, 0, -1, 0, page.GetPageSize().GetHeight());
|
|
||||||
});
|
|
||||||
|
|
||||||
foreach (var signature in signatures)
|
foreach (var signature in signatures)
|
||||||
Page(signature.Page, page =>
|
Page(signature.Page, page =>
|
||||||
{
|
{
|
||||||
var canvas = new PdfCanvas(page);
|
var canvas = new PdfCanvas(page);
|
||||||
|
canvas.SaveState();
|
||||||
|
canvas.ConcatMatrix(1, 0, 0, -1, 0, page.GetPageSize().GetHeight());
|
||||||
|
|
||||||
double inchFactor = 72;
|
double inchFactor = 72;
|
||||||
double magin = .2;
|
double magin = .2;
|
||||||
double x = (signature.X - .7 - magin) * inchFactor;
|
double x = (signature.X - .7 - magin) * inchFactor;
|
||||||
@@ -134,6 +130,8 @@ namespace EnvelopeGenerator.PdfEditor
|
|||||||
canvas.SetFillColor(new DeviceRgb(204, 202, 198))
|
canvas.SetFillColor(new DeviceRgb(204, 202, 198))
|
||||||
.Rectangle(x, y + height - bottomLineLength, width, bottomLineLength)
|
.Rectangle(x, y + height - bottomLineLength, width, bottomLineLength)
|
||||||
.Fill();
|
.Fill();
|
||||||
|
|
||||||
|
canvas.RestoreState();
|
||||||
});
|
});
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
using System.Security.Claims;
|
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
|
||||||
using EnvelopeGenerator.ReceiverUI.Client.Services;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Auth;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fragt die API, ob der Nutzer eingeloggt ist.
|
|
||||||
///
|
|
||||||
/// WARUM nicht selbst Token lesen?
|
|
||||||
/// - Das Auth-Cookie ist HttpOnly → JavaScript/WASM kann es nicht lesen
|
|
||||||
/// - Stattdessen: Frage die API "bin ich eingeloggt?" → GET /api/auth/check
|
|
||||||
/// - Die API prüft das Cookie serverseitig und antwortet mit 200 oder 401
|
|
||||||
/// </summary>
|
|
||||||
public class ApiAuthStateProvider : AuthenticationStateProvider
|
|
||||||
{
|
|
||||||
private readonly IAuthService _authService;
|
|
||||||
|
|
||||||
public ApiAuthStateProvider(IAuthService authService)
|
|
||||||
{
|
|
||||||
_authService = authService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
|
|
||||||
{
|
|
||||||
var result = await _authService.CheckAuthAsync();
|
|
||||||
|
|
||||||
if (result.IsSuccess)
|
|
||||||
{
|
|
||||||
// Eingeloggt → Erstelle einen authentifizierten ClaimsPrincipal
|
|
||||||
var identity = new ClaimsIdentity("cookie");
|
|
||||||
return new AuthenticationState(new ClaimsPrincipal(identity));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nicht eingeloggt
|
|
||||||
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wird nach Login/Logout aufgerufen, damit Blazor den Auth-State aktualisiert.
|
|
||||||
/// </summary>
|
|
||||||
public void NotifyAuthChanged()
|
|
||||||
{
|
|
||||||
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
@* AccessCodeForm: Zeigt das Zugangscode-Eingabeformular.
|
|
||||||
Entspricht dem AccessCode-Teil von EnvelopeLocked.cshtml im Web-Projekt.
|
|
||||||
|
|
||||||
"Dumme" Komponente: Keine Services, keine API-Calls.
|
|
||||||
- Bekommt Daten über [Parameter]
|
|
||||||
- Gibt Eingaben über EventCallback an die Eltern-Page zurück
|
|
||||||
- Die Page entscheidet, was mit dem Code passiert *@
|
|
||||||
|
|
||||||
<div class="page">
|
|
||||||
@* ── Header: Icon + Titel ── *@
|
|
||||||
<header class="text-center">
|
|
||||||
<div class="status-icon locked mt-4 mb-1">
|
|
||||||
<i class="bi bi-shield-lock"></i>
|
|
||||||
</div>
|
|
||||||
<h1>Zugangscode eingeben</h1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
@* ── Erklärungstext ── *@
|
|
||||||
<section class="text-center mb-4">
|
|
||||||
<p class="text-muted">
|
|
||||||
Ein Zugangscode wurde an Ihre E-Mail-Adresse gesendet.
|
|
||||||
Bitte geben Sie diesen unten ein.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
@* ── Formular ── *@
|
|
||||||
<div class="access-code-container">
|
|
||||||
<EditForm Model="_model" OnValidSubmit="Submit">
|
|
||||||
<DataAnnotationsValidator />
|
|
||||||
|
|
||||||
@* Code-Eingabefeld: type="password" damit niemand mitlesen kann *@
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<InputText @bind-Value="_model.Code"
|
|
||||||
type="password"
|
|
||||||
class="form-control code-input"
|
|
||||||
id="accessCodeInput"
|
|
||||||
placeholder="Zugangscode" />
|
|
||||||
<label for="accessCodeInput">Zugangscode</label>
|
|
||||||
<ValidationMessage For="() => _model.Code" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* TFA-Switch: Nur sichtbar wenn der Umschlag TFA aktiviert hat.
|
|
||||||
Im Web-Projekt ist das der "2FA per SMS"-Toggle.
|
|
||||||
Disabled wenn der Empfänger keine Telefonnummer hat. *@
|
|
||||||
@if (TfaEnabled)
|
|
||||||
{
|
|
||||||
<div class="form-check form-switch mb-3">
|
|
||||||
<input class="form-check-input"
|
|
||||||
type="checkbox"
|
|
||||||
id="preferSmsSwitch"
|
|
||||||
checked="@_preferSms"
|
|
||||||
disabled="@(!HasPhoneNumber)"
|
|
||||||
@onchange="ToggleSms" />
|
|
||||||
<label class="form-check-label" for="preferSmsSwitch">
|
|
||||||
2FA per SMS
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@* Fehlermeldung: Wird von der Eltern-Page gesetzt
|
|
||||||
wenn der AccessCode falsch war *@
|
|
||||||
@if (!string.IsNullOrEmpty(ErrorMessage))
|
|
||||||
{
|
|
||||||
<div class="alert alert-danger">@ErrorMessage</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@* Submit-Button mit Loading-State *@
|
|
||||||
<button type="submit" class="btn btn-primary w-100" disabled="@_isSubmitting">
|
|
||||||
@if (_isSubmitting)
|
|
||||||
{
|
|
||||||
<LoadingIndicator Small="true" />
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<i class="bi bi-box-arrow-in-right me-2"></i>
|
|
||||||
<span>Bestätigen</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</EditForm>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* ── Hilfe-Bereich: Wer hat dieses Dokument gesendet? ──
|
|
||||||
Im Web-Projekt ist das der <details>-Bereich ganz unten.
|
|
||||||
Zeigt Sender-Mail und Umschlag-Titel, damit der Empfänger
|
|
||||||
bei Problemen weiß, an wen er sich wenden kann. *@
|
|
||||||
@if (!string.IsNullOrEmpty(SenderEmail))
|
|
||||||
{
|
|
||||||
<section class="text-center mt-4">
|
|
||||||
<details>
|
|
||||||
<summary class="text-muted">Woher kommt dieser Code?</summary>
|
|
||||||
<p class="text-muted mt-2">
|
|
||||||
Dieses Dokument
|
|
||||||
@if (!string.IsNullOrEmpty(Title))
|
|
||||||
{
|
|
||||||
<span>«@Title» </span>
|
|
||||||
}
|
|
||||||
wurde Ihnen von <a href="mailto:@SenderEmail">@SenderEmail</a> zugesendet.
|
|
||||||
Der Zugangscode wurde ebenfalls an Ihre E-Mail-Adresse geschickt.
|
|
||||||
</p>
|
|
||||||
</details>
|
|
||||||
</section>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
// ── Parameter von der Eltern-Page ──
|
|
||||||
|
|
||||||
/// <summary>Der Envelope-Key aus der URL</summary>
|
|
||||||
[Parameter, EditorRequired]
|
|
||||||
public string EnvelopeKey { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>Fehlermeldung (z.B. "Falscher Zugangscode")</summary>
|
|
||||||
[Parameter]
|
|
||||||
public string? ErrorMessage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>E-Mail des Absenders — für den Hilfe-Bereich unten</summary>
|
|
||||||
[Parameter]
|
|
||||||
public string? SenderEmail { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Titel des Umschlags — für den Hilfe-Bereich unten</summary>
|
|
||||||
[Parameter]
|
|
||||||
public string? Title { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Ob TFA für diesen Umschlag aktiviert ist — zeigt den SMS-Switch</summary>
|
|
||||||
[Parameter]
|
|
||||||
public bool TfaEnabled { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Ob der Empfänger eine Telefonnummer hat — sonst ist SMS-Switch disabled</summary>
|
|
||||||
[Parameter]
|
|
||||||
public bool HasPhoneNumber { get; set; }
|
|
||||||
|
|
||||||
// ── Event: Gibt Code + SMS-Präferenz an die Page zurück ──
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback wenn der Benutzer das Formular abschickt.
|
|
||||||
/// Gibt ein Tuple zurück: (Code, PreferSms)
|
|
||||||
/// Die Page entscheidet dann, was damit passiert (API-Call etc.)
|
|
||||||
///
|
|
||||||
/// WARUM ein Tuple statt nur string?
|
|
||||||
/// Weil die Page auch wissen muss, ob der Benutzer SMS bevorzugt.
|
|
||||||
/// Im Web-Projekt wird das als separates Form-Feld "userSelectSMS" gesendet.
|
|
||||||
/// </summary>
|
|
||||||
[Parameter]
|
|
||||||
public EventCallback<(string Code, bool PreferSms)> OnSubmit { get; set; }
|
|
||||||
|
|
||||||
// ── Interner State ──
|
|
||||||
|
|
||||||
private AccessCodeModel _model = new();
|
|
||||||
private bool _isSubmitting;
|
|
||||||
private bool _preferSms;
|
|
||||||
|
|
||||||
private void ToggleSms(ChangeEventArgs e)
|
|
||||||
{
|
|
||||||
_preferSms = (bool)(e.Value ?? false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Submit()
|
|
||||||
{
|
|
||||||
_isSubmitting = true;
|
|
||||||
await OnSubmit.InvokeAsync((_model.Code, _preferSms));
|
|
||||||
_isSubmitting = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Validierungs-Model ──
|
|
||||||
|
|
||||||
private class AccessCodeModel
|
|
||||||
{
|
|
||||||
[System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Bitte Zugangscode eingeben")]
|
|
||||||
[System.ComponentModel.DataAnnotations.StringLength(6, MinimumLength = 4)]
|
|
||||||
public string Code { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
@* ActionPanel: Fixierte Button-Leiste am unteren Bildschirmrand.
|
|
||||||
Entspricht dem <div id="flex-action-panel"> in ShowEnvelope.cshtml.
|
|
||||||
|
|
||||||
Wird angezeigt wenn der Empfänger das Dokument sieht (Status = ShowDocument).
|
|
||||||
Bei ReadOnly-Dokumenten wird nichts angezeigt.
|
|
||||||
|
|
||||||
Die Buttons lösen EventCallbacks aus. Die Eltern-Komponente (EnvelopePage)
|
|
||||||
entscheidet, was passiert (API-Call etc.).
|
|
||||||
|
|
||||||
Der ConfirmDialog wird über eine Referenz aufgerufen:
|
|
||||||
var confirmed = await _confirmDialog.ShowAsync("Titel", "Text");
|
|
||||||
|
|
||||||
WARUM EventCallbacks statt direkte API-Calls?
|
|
||||||
- Die Komponente bleibt "dumm" (keine Services, kein API-Wissen)
|
|
||||||
- Die Eltern-Page kann den gleichen Button-Click anders behandeln
|
|
||||||
- Einfacher zu testen *@
|
|
||||||
|
|
||||||
@if (!ReadOnly)
|
|
||||||
{
|
|
||||||
@* position-fixed: Bleibt am unteren Rand auch beim Scrollen.
|
|
||||||
Gleiche Positionierung wie im Web-Projekt. *@
|
|
||||||
<div class="position-fixed bottom-0 end-0 p-3 d-flex gap-2" style="z-index: 1050;">
|
|
||||||
|
|
||||||
@* Zurücksetzen-Button: Setzt alle Signaturen zurück.
|
|
||||||
Im Web-Projekt ist das der graue Button mit dem
|
|
||||||
Pfeil-gegen-den-Uhrzeigersinn-Icon. *@
|
|
||||||
<button class="btn btn-secondary" @onclick="HandleRefresh" title="Zurücksetzen">
|
|
||||||
<i class="bi bi-arrow-counterclockwise"></i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
@* Ablehnen-Button: Öffnet ConfirmDialog, dann EventCallback.
|
|
||||||
Im Web-Projekt ist das der rote Button mit dem X-Icon. *@
|
|
||||||
<button class="btn btn-danger" @onclick="HandleReject" title="Ablehnen">
|
|
||||||
<i class="bi bi-x-lg me-1"></i>
|
|
||||||
<span class="d-none d-md-inline">Ablehnen</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
@* Unterschreiben-Button: Öffnet ConfirmDialog, dann EventCallback.
|
|
||||||
Im Web-Projekt ist das der grüne Button mit dem Briefumschlag-Icon. *@
|
|
||||||
<button class="btn btn-success" @onclick="HandleSign" title="Unterschreiben">
|
|
||||||
<i class="bi bi-pen me-1"></i>
|
|
||||||
<span class="d-none d-md-inline">Unterschreiben</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@* ConfirmDialog: Wird nur gerendert wenn nötig (wenn ShowAsync aufgerufen wird).
|
|
||||||
Die Referenz (_confirmDialog) erlaubt uns, ShowAsync von den Button-Handlern aufzurufen. *@
|
|
||||||
<ConfirmDialog @ref="_confirmDialog" />
|
|
||||||
|
|
||||||
@code {
|
|
||||||
// ── Parameter ──
|
|
||||||
|
|
||||||
/// <summary>Bei ReadOnly wird das gesamte Panel ausgeblendet</summary>
|
|
||||||
[Parameter]
|
|
||||||
public bool ReadOnly { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Wird ausgelöst wenn der Benutzer "Unterschreiben" bestätigt</summary>
|
|
||||||
[Parameter]
|
|
||||||
public EventCallback OnSign { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Wird ausgelöst wenn der Benutzer "Ablehnen" bestätigt</summary>
|
|
||||||
[Parameter]
|
|
||||||
public EventCallback OnReject { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Wird ausgelöst wenn der Benutzer "Zurücksetzen" klickt</summary>
|
|
||||||
[Parameter]
|
|
||||||
public EventCallback OnRefresh { get; set; }
|
|
||||||
|
|
||||||
// ── Referenz auf den ConfirmDialog ──
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Referenz auf die ConfirmDialog-Komponente.
|
|
||||||
///
|
|
||||||
/// WAS IST @ref?
|
|
||||||
/// Mit @ref speichert Blazor eine Referenz auf eine Kind-Komponente.
|
|
||||||
/// Dann kann man Methoden darauf aufrufen: _confirmDialog.ShowAsync(...)
|
|
||||||
/// Das ist wie document.getElementById() in JavaScript, nur typsicher.
|
|
||||||
/// </summary>
|
|
||||||
private ConfirmDialog _confirmDialog = default!;
|
|
||||||
|
|
||||||
// ── Button-Handler ──
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Unterschreiben: Erst bestätigen, dann Event auslösen.
|
|
||||||
/// Der ConfirmDialog zeigt "Möchten Sie das Dokument unterschreiben?"
|
|
||||||
/// Nur wenn der Benutzer "Ja" klickt, wird OnSign ausgelöst.
|
|
||||||
/// </summary>
|
|
||||||
private async Task HandleSign()
|
|
||||||
{
|
|
||||||
var confirmed = await _confirmDialog.ShowAsync(
|
|
||||||
"Unterschreiben",
|
|
||||||
"Möchten Sie das Dokument wirklich unterschreiben? Diese Aktion kann nicht rückgängig gemacht werden.",
|
|
||||||
confirmText: "Unterschreiben",
|
|
||||||
confirmColor: "success");
|
|
||||||
|
|
||||||
if (confirmed)
|
|
||||||
await OnSign.InvokeAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ablehnen: Erst bestätigen, dann Event auslösen.
|
|
||||||
/// Roter Button im ConfirmDialog, weil Ablehnen destruktiv ist.
|
|
||||||
/// </summary>
|
|
||||||
private async Task HandleReject()
|
|
||||||
{
|
|
||||||
var confirmed = await _confirmDialog.ShowAsync(
|
|
||||||
"Ablehnen",
|
|
||||||
"Möchten Sie das Dokument wirklich ablehnen? Diese Aktion kann nicht rückgängig gemacht werden.",
|
|
||||||
confirmText: "Ablehnen",
|
|
||||||
confirmColor: "danger");
|
|
||||||
|
|
||||||
if (confirmed)
|
|
||||||
await OnReject.InvokeAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Zurücksetzen: Kein ConfirmDialog nötig, weil es nicht destruktiv ist.
|
|
||||||
/// Setzt nur die Signatur-Positionen zurück, der Empfänger kann danach
|
|
||||||
/// erneut signieren.
|
|
||||||
/// </summary>
|
|
||||||
private async Task HandleRefresh()
|
|
||||||
{
|
|
||||||
await OnRefresh.InvokeAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
@* EnvelopeInfoCard: Zeigt die Umschlag-Infos oberhalb des PDF-Viewers.
|
|
||||||
Entspricht dem Card-Bereich in ShowEnvelope.cshtml im Web-Projekt.
|
|
||||||
|
|
||||||
Wird angezeigt wenn der Empfänger erfolgreich authentifiziert ist
|
|
||||||
und das Dokument sehen darf.
|
|
||||||
|
|
||||||
"Dumme" Komponente: Keine Services, nur Parameter → Anzeige. *@
|
|
||||||
|
|
||||||
<div class="card mb-3">
|
|
||||||
@* ── Card Header: Logo + Fortschrittsbalken ──
|
|
||||||
Im Web-Projekt gibt es hier das signFLOW-Logo und darunter
|
|
||||||
"2/3 Signatures". Wir zeigen den Fortschritt nur wenn es
|
|
||||||
KEIN ReadOnly-Dokument ist (ReadOnly = nur lesen, nicht signieren). *@
|
|
||||||
<div class="card-header bg-white">
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<strong class="text-primary">signFLOW</strong>
|
|
||||||
|
|
||||||
@if (!ReadOnly && SignatureTotal > 0)
|
|
||||||
{
|
|
||||||
<div class="d-flex align-items-center gap-2">
|
|
||||||
<div class="progress" style="width: 120px; height: 8px;">
|
|
||||||
<div class="progress-bar bg-success"
|
|
||||||
role="progressbar"
|
|
||||||
style="width: @ProgressPercent%"
|
|
||||||
aria-valuenow="@SignaturesDone"
|
|
||||||
aria-valuemin="0"
|
|
||||||
aria-valuemax="@SignatureTotal">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<small class="text-muted">@SignaturesDone/@SignatureTotal</small>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (ReadOnly)
|
|
||||||
{
|
|
||||||
<span class="badge bg-secondary">Nur Ansicht</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* ── Card Body: Titel, Nachricht, Sender-Info ── *@
|
|
||||||
<div class="card-body">
|
|
||||||
@* Titel des Umschlags *@
|
|
||||||
<h5 class="card-title">@Title</h5>
|
|
||||||
|
|
||||||
@* Nachricht des Absenders — im Web-Projekt wird das mit Marked.js
|
|
||||||
als Markdown gerendert. Hier erstmal als Plain Text.
|
|
||||||
Markdown-Rendering kommt in Phase 6 (Feinschliff). *@
|
|
||||||
@if (!string.IsNullOrEmpty(Message))
|
|
||||||
{
|
|
||||||
<p class="card-text">@Message</p>
|
|
||||||
}
|
|
||||||
|
|
||||||
@* Sender-Info: Wer hat es gesendet, wann?
|
|
||||||
Im Web-Projekt steht hier:
|
|
||||||
"Gesendet am 15.03.2026 von Max Mustermann (max@firma.de)" *@
|
|
||||||
<p class="card-text">
|
|
||||||
<small class="text-muted">
|
|
||||||
@if (!string.IsNullOrEmpty(SenderName) && !string.IsNullOrEmpty(SenderEmail))
|
|
||||||
{
|
|
||||||
<span>
|
|
||||||
Gesendet
|
|
||||||
@if (SentDate is not null)
|
|
||||||
{
|
|
||||||
<span>am @SentDate.Value.ToString("dd.MM.yyyy")</span>
|
|
||||||
}
|
|
||||||
von @SenderName
|
|
||||||
(<a href="mailto:@SenderEmail">@SenderEmail</a>)
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</small>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
// ── Parameter ──
|
|
||||||
|
|
||||||
/// <summary>Titel des Umschlags (z.B. "Vertragsdokument")</summary>
|
|
||||||
[Parameter, EditorRequired]
|
|
||||||
public string Title { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>Nachricht des Absenders</summary>
|
|
||||||
[Parameter]
|
|
||||||
public string? Message { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Name des Absenders (z.B. "Max Mustermann")</summary>
|
|
||||||
[Parameter]
|
|
||||||
public string? SenderName { get; set; }
|
|
||||||
|
|
||||||
/// <summary>E-Mail des Absenders</summary>
|
|
||||||
[Parameter]
|
|
||||||
public string? SenderEmail { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Datum an dem der Umschlag gesendet wurde</summary>
|
|
||||||
[Parameter]
|
|
||||||
public DateTime? SentDate { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Ob das Dokument nur zum Lesen ist (kein Signieren)</summary>
|
|
||||||
[Parameter]
|
|
||||||
public bool ReadOnly { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Anzahl bereits geleisteter Unterschriften</summary>
|
|
||||||
[Parameter]
|
|
||||||
public int SignaturesDone { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Gesamtanzahl benötigter Unterschriften</summary>
|
|
||||||
[Parameter]
|
|
||||||
public int SignatureTotal { get; set; }
|
|
||||||
|
|
||||||
// ── Berechnete Werte ──
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fortschritt in Prozent für den Balken.
|
|
||||||
/// Vermeidet Division durch Null.
|
|
||||||
/// </summary>
|
|
||||||
private int ProgressPercent =>
|
|
||||||
SignatureTotal > 0
|
|
||||||
? (int)((double)SignaturesDone / SignatureTotal * 100)
|
|
||||||
: 0;
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
@inject IJSRuntime JS
|
|
||||||
@implements IAsyncDisposable
|
|
||||||
|
|
||||||
<div id="pspdfkit-container" class="pdf-container" style="width: 100%; height: 80vh;"></div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter] public byte[]? DocumentBytes { get; set; }
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
if (firstRender && DocumentBytes is not null)
|
|
||||||
{
|
|
||||||
// TODO: PSPDFKit JS-Interop implementieren (Phase 6)
|
|
||||||
// await JS.InvokeVoidAsync("initPdfViewer", DocumentBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
// TODO: PSPDFKit aufräumen
|
|
||||||
// await JS.InvokeVoidAsync("destroyPdfViewer");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<h3>SignaturePanel</h3>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
@* TfaForm: Zwei-Faktor-Authentifizierung — SMS oder Authenticator.
|
|
||||||
Entspricht dem TFA-Teil von EnvelopeLocked.cshtml im Web-Projekt.
|
|
||||||
|
|
||||||
Wird angezeigt NACH dem AccessCode-Schritt, wenn der Umschlag TFA erfordert.
|
|
||||||
|
|
||||||
Zwei Varianten:
|
|
||||||
- TfaType="sms" → SMS wurde gesendet, Countdown-Timer zeigt verbleibende Zeit
|
|
||||||
- TfaType="authenticator" → Benutzer gibt Code aus seiner Authenticator-App ein
|
|
||||||
|
|
||||||
"Dumme" Komponente: Keine Services, keine API-Calls. *@
|
|
||||||
|
|
||||||
@implements IDisposable
|
|
||||||
|
|
||||||
<div class="page">
|
|
||||||
@* ── Header: Icon + Titel ── *@
|
|
||||||
<header class="text-center">
|
|
||||||
<div class="status-icon locked mt-4 mb-1">
|
|
||||||
<i class="bi bi-shield-check"></i>
|
|
||||||
</div>
|
|
||||||
<h1>Zwei-Faktor-Authentifizierung</h1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
@* ── Erklärungstext: Unterschiedlich je nach TFA-Typ ── *@
|
|
||||||
<section class="text-center mb-4">
|
|
||||||
@if (TfaType == "sms")
|
|
||||||
{
|
|
||||||
<p class="text-muted">
|
|
||||||
Ein Bestätigungscode wurde per SMS an Ihre Telefonnummer gesendet.
|
|
||||||
</p>
|
|
||||||
@* SMS-Countdown-Timer: Zeigt wie lange der Code noch gültig ist.
|
|
||||||
Im Web-Projekt ist das JavaScript (setInterval).
|
|
||||||
Hier machen wir es mit einem C#-Timer — kein JS nötig. *@
|
|
||||||
@if (_remainingTime is not null)
|
|
||||||
{
|
|
||||||
<div class="alert @(_remainingTime > TimeSpan.Zero ? "alert-primary" : "alert-warning")">
|
|
||||||
@if (_remainingTime > TimeSpan.Zero)
|
|
||||||
{
|
|
||||||
<span>Code gültig für: @_remainingTime.Value.ToString("mm\\:ss")</span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span>Code abgelaufen. Bitte fordern Sie einen neuen Code an.</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<p class="text-muted">
|
|
||||||
Öffnen Sie Ihre Authenticator-App und geben Sie den angezeigten Code ein.
|
|
||||||
</p>
|
|
||||||
}
|
|
||||||
</section>
|
|
||||||
|
|
||||||
@* ── Formular ── *@
|
|
||||||
<div class="access-code-container">
|
|
||||||
<EditForm Model="_model" OnValidSubmit="Submit">
|
|
||||||
<DataAnnotationsValidator />
|
|
||||||
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<InputText @bind-Value="_model.Code"
|
|
||||||
type="password"
|
|
||||||
class="form-control code-input"
|
|
||||||
id="tfaCodeInput"
|
|
||||||
placeholder="Code" />
|
|
||||||
<label for="tfaCodeInput">
|
|
||||||
@(TfaType == "sms" ? "SMS-Code" : "Authenticator-Code")
|
|
||||||
</label>
|
|
||||||
<ValidationMessage For="() => _model.Code" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (!string.IsNullOrEmpty(ErrorMessage))
|
|
||||||
{
|
|
||||||
<div class="alert alert-danger">@ErrorMessage</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<button type="submit"
|
|
||||||
class="btn btn-primary w-100"
|
|
||||||
disabled="@(_isSubmitting || _remainingTime <= TimeSpan.Zero)">
|
|
||||||
@if (_isSubmitting)
|
|
||||||
{
|
|
||||||
<LoadingIndicator Small="true" />
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<i class="bi bi-unlock me-2"></i>
|
|
||||||
<span>Bestätigen</span>
|
|
||||||
}
|
|
||||||
</button>
|
|
||||||
</EditForm>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
// ── Parameter von der Eltern-Page ──
|
|
||||||
|
|
||||||
/// <summary>Der Envelope-Key aus der URL</summary>
|
|
||||||
[Parameter, EditorRequired]
|
|
||||||
public string EnvelopeKey { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// "sms" oder "authenticator" — bestimmt welche Variante angezeigt wird.
|
|
||||||
/// Kommt aus der API-Antwort nach dem AccessCode-Schritt.
|
|
||||||
/// </summary>
|
|
||||||
[Parameter, EditorRequired]
|
|
||||||
public string TfaType { get; set; } = "authenticator";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ablaufzeit des SMS-Codes. Nur bei TfaType="sms" relevant.
|
|
||||||
/// Der Timer zählt von jetzt bis zu diesem Zeitpunkt runter.
|
|
||||||
/// </summary>
|
|
||||||
[Parameter]
|
|
||||||
public DateTime? TfaExpiration { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Ob der Empfänger eine Telefonnummer hat</summary>
|
|
||||||
[Parameter]
|
|
||||||
public bool HasPhoneNumber { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Fehlermeldung (z.B. "Falscher Code")</summary>
|
|
||||||
[Parameter]
|
|
||||||
public string? ErrorMessage { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback: Gibt (Code, Type) an die Page zurück.
|
|
||||||
/// Die Page sendet das dann an die API.
|
|
||||||
/// </summary>
|
|
||||||
[Parameter]
|
|
||||||
public EventCallback<(string Code, string Type)> OnSubmit { get; set; }
|
|
||||||
|
|
||||||
// ── Interner State ──
|
|
||||||
|
|
||||||
private TfaCodeModel _model = new();
|
|
||||||
private bool _isSubmitting;
|
|
||||||
private TimeSpan? _remainingTime;
|
|
||||||
private System.Threading.Timer? _timer;
|
|
||||||
|
|
||||||
// ── Lifecycle ──
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wird aufgerufen wenn die Komponente initialisiert wird.
|
|
||||||
/// Startet den SMS-Countdown-Timer falls nötig.
|
|
||||||
///
|
|
||||||
/// OnInitialized (nicht Async) reicht hier, weil wir keinen
|
|
||||||
/// await brauchen — der Timer läuft über einen Callback.
|
|
||||||
/// </summary>
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
if (TfaType == "sms" && TfaExpiration is not null)
|
|
||||||
{
|
|
||||||
// Restzeit berechnen
|
|
||||||
_remainingTime = TfaExpiration.Value - DateTime.Now;
|
|
||||||
|
|
||||||
// Timer: Jede Sekunde _remainingTime aktualisieren.
|
|
||||||
// InvokeAsync + StateHasChanged sagt Blazor: "Zeichne die UI neu".
|
|
||||||
// Das ist das Blazor-Äquivalent zu setInterval + DOM-Update in JavaScript.
|
|
||||||
_timer = new System.Threading.Timer(_ =>
|
|
||||||
{
|
|
||||||
_remainingTime = TfaExpiration.Value - DateTime.Now;
|
|
||||||
|
|
||||||
if (_remainingTime <= TimeSpan.Zero)
|
|
||||||
{
|
|
||||||
_remainingTime = TimeSpan.Zero;
|
|
||||||
_timer?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvokeAsync ist nötig, weil der Timer auf einem anderen Thread läuft.
|
|
||||||
// Blazor erlaubt UI-Updates nur auf dem UI-Thread.
|
|
||||||
InvokeAsync(StateHasChanged);
|
|
||||||
},
|
|
||||||
state: null,
|
|
||||||
dueTime: TimeSpan.Zero, // Sofort starten
|
|
||||||
period: TimeSpan.FromSeconds(1) // Jede Sekunde
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Submit()
|
|
||||||
{
|
|
||||||
_isSubmitting = true;
|
|
||||||
await OnSubmit.InvokeAsync((_model.Code, TfaType));
|
|
||||||
_isSubmitting = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Aufräumen: Timer stoppen wenn die Komponente entfernt wird.
|
|
||||||
/// Ohne Dispose würde der Timer weiterlaufen und Fehler verursachen,
|
|
||||||
/// weil er versucht eine nicht mehr existierende UI zu aktualisieren.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_timer?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Validierungs-Model ──
|
|
||||||
|
|
||||||
private class TfaCodeModel
|
|
||||||
{
|
|
||||||
[System.ComponentModel.DataAnnotations.Required(ErrorMessage = "Bitte Code eingeben")]
|
|
||||||
[System.ComponentModel.DataAnnotations.StringLength(6, MinimumLength = 6, ErrorMessage = "Der Code muss 6 Zeichen lang sein")]
|
|
||||||
public string Code { get; set; } = string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<h3>TwoFactorForm</h3>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<h3>NavHeader</h3>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<h3>AlertMessage</h3>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
@* ConfirmDialog: Ersetzt SweetAlert2 aus dem Web-Projekt.
|
|
||||||
|
|
||||||
Zeigt einen modalen Bestätigungsdialog mit Titel, Text und Ja/Nein-Buttons.
|
|
||||||
Wird NICHT über Parameter gesteuert, sondern über eine Methode:
|
|
||||||
|
|
||||||
var confirmed = await _dialog.ShowAsync("Titel", "Text");
|
|
||||||
|
|
||||||
WARUM eine Methode statt Parameter?
|
|
||||||
- Ein Dialog ist ein "einmaliges Ereignis", kein dauerhafter Zustand
|
|
||||||
- Die aufrufende Komponente will auf das Ergebnis WARTEN (await)
|
|
||||||
- Mit Parametern müsste man den State manuell hin- und herschalten
|
|
||||||
|
|
||||||
WARUM kein SweetAlert2?
|
|
||||||
- SweetAlert2 ist eine JavaScript-Bibliothek
|
|
||||||
- In Blazor können wir das nativ in C# lösen, ohne JS-Interop
|
|
||||||
- Weniger Abhängigkeiten = weniger Wartung = weniger Fehlerquellen *@
|
|
||||||
|
|
||||||
@if (_isVisible)
|
|
||||||
{
|
|
||||||
@* Hintergrund-Overlay: Dunkler Hintergrund hinter dem Dialog.
|
|
||||||
Im Web-Projekt macht SweetAlert2 das automatisch.
|
|
||||||
Hier bauen wir es selbst mit CSS. *@
|
|
||||||
<div class="modal-backdrop fade show"></div>
|
|
||||||
|
|
||||||
@* Modal-Dialog: Bootstrap 5 Modal-Markup.
|
|
||||||
Normalerweise braucht Bootstrap-Modal JavaScript (bootstrap.js)
|
|
||||||
um zu öffnen/schließen. Wir steuern die Sichtbarkeit stattdessen
|
|
||||||
über die _isVisible-Variable — Blazor macht das DOM-Update. *@
|
|
||||||
<div class="modal fade show d-block" tabindex="-1" role="dialog">
|
|
||||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
|
|
||||||
@* Header: Titel + Schließen-Button *@
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">@_title</h5>
|
|
||||||
<button type="button" class="btn-close" @onclick="Cancel"></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Body: Beschreibungstext *@
|
|
||||||
<div class="modal-body">
|
|
||||||
<p>@_message</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Footer: Abbrechen + Bestätigen *@
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">
|
|
||||||
@_cancelText
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-@_confirmColor" @onclick="Confirm">
|
|
||||||
@_confirmText
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
|
||||||
// ── Interner State (NICHT Parameter — wird über ShowAsync gesetzt) ──
|
|
||||||
|
|
||||||
private bool _isVisible;
|
|
||||||
private string _title = string.Empty;
|
|
||||||
private string _message = string.Empty;
|
|
||||||
private string _confirmText = "Ja";
|
|
||||||
private string _cancelText = "Abbrechen";
|
|
||||||
private string _confirmColor = "primary";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// TaskCompletionSource: Das Herzstück dieser Komponente.
|
|
||||||
///
|
|
||||||
/// WAS IST DAS?
|
|
||||||
/// Ein TaskCompletionSource erstellt einen Task, der erst dann
|
|
||||||
/// "fertig" wird, wenn jemand SetResult() aufruft.
|
|
||||||
///
|
|
||||||
/// WIE FUNKTIONIERT ES?
|
|
||||||
/// 1. ShowAsync() erstellt ein neues TaskCompletionSource
|
|
||||||
/// 2. ShowAsync() gibt dessen Task zurück → der Aufrufer wartet (await)
|
|
||||||
/// 3. Der Benutzer klickt "Ja" → Confirm() ruft SetResult(true) auf
|
|
||||||
/// 4. Der await in der aufrufenden Komponente kommt zurück mit true
|
|
||||||
///
|
|
||||||
/// Das ist wie ein "Promise" in JavaScript, nur in C#.
|
|
||||||
/// </summary>
|
|
||||||
private TaskCompletionSource<bool>? _tcs;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Zeigt den Dialog und wartet auf die Benutzer-Entscheidung.
|
|
||||||
///
|
|
||||||
/// Beispiel-Aufruf:
|
|
||||||
/// var confirmed = await _dialog.ShowAsync(
|
|
||||||
/// "Unterschreiben",
|
|
||||||
/// "Möchten Sie das Dokument wirklich unterschreiben?");
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="title">Dialog-Überschrift</param>
|
|
||||||
/// <param name="message">Beschreibungstext</param>
|
|
||||||
/// <param name="confirmText">Text auf dem Bestätigen-Button (Standard: "Ja")</param>
|
|
||||||
/// <param name="cancelText">Text auf dem Abbrechen-Button (Standard: "Abbrechen")</param>
|
|
||||||
/// <param name="confirmColor">Bootstrap-Farbe des Bestätigen-Buttons (Standard: "primary")</param>
|
|
||||||
/// <returns>true wenn bestätigt, false wenn abgebrochen</returns>
|
|
||||||
public Task<bool> ShowAsync(
|
|
||||||
string title,
|
|
||||||
string message,
|
|
||||||
string confirmText = "Ja",
|
|
||||||
string cancelText = "Abbrechen",
|
|
||||||
string confirmColor = "primary")
|
|
||||||
{
|
|
||||||
_title = title;
|
|
||||||
_message = message;
|
|
||||||
_confirmText = confirmText;
|
|
||||||
_cancelText = cancelText;
|
|
||||||
_confirmColor = confirmColor;
|
|
||||||
|
|
||||||
_tcs = new TaskCompletionSource<bool>();
|
|
||||||
_isVisible = true;
|
|
||||||
StateHasChanged();
|
|
||||||
|
|
||||||
return _tcs.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Confirm()
|
|
||||||
{
|
|
||||||
_isVisible = false;
|
|
||||||
_tcs?.TrySetResult(true);
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Cancel()
|
|
||||||
{
|
|
||||||
_isVisible = false;
|
|
||||||
_tcs?.TrySetResult(false);
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<div class="text-center py-5">
|
|
||||||
@if (!string.IsNullOrEmpty(Icon))
|
|
||||||
{
|
|
||||||
<div class="mb-3">
|
|
||||||
<i class="bi bi-@Icon" style="font-size: 3rem;"></i>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
<h2>@Title</h2>
|
|
||||||
@if (!string.IsNullOrEmpty(Message))
|
|
||||||
{
|
|
||||||
<p class="text-muted">@Message</p>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter] public string Title { get; set; } = "Fehler";
|
|
||||||
[Parameter] public string? Message { get; set; }
|
|
||||||
[Parameter] public string? Icon { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<h3>LanguageSelector</h3>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<div class="d-flex justify-content-center align-items-center @(Small ? "" : "py-5")" style="@(Small ? "" : "min-height: 40vh;")">
|
|
||||||
<div class="text-center">
|
|
||||||
<div class="spinner-border @(Small ? "spinner-border-sm" : "text-primary")"
|
|
||||||
style="@(Small ? "" : "width: 3rem; height: 3rem;")"
|
|
||||||
role="status">
|
|
||||||
<span class="visually-hidden">Laden...</span>
|
|
||||||
</div>
|
|
||||||
@if (!Small && Message is not null)
|
|
||||||
{
|
|
||||||
<p class="mt-3 text-muted">@Message</p>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter] public bool Small { get; set; }
|
|
||||||
[Parameter] public string? Message { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
@* StatusPage: Wiederverwendbare Status-Seite für alle Endzustände.
|
|
||||||
Ersetzt EnvelopeSigned.cshtml, EnvelopeRejected.cshtml, Error-Views und Expired.
|
|
||||||
"Dumme" Komponente: Keine Services, keine API-Calls, nur Parameter → Anzeige. *@
|
|
||||||
|
|
||||||
<div class="page">
|
|
||||||
<header class="text-center">
|
|
||||||
@switch (Type)
|
|
||||||
{
|
|
||||||
case "signed":
|
|
||||||
<div class="status-icon signed">
|
|
||||||
<i class="bi bi-check-circle"></i>
|
|
||||||
</div>
|
|
||||||
<h1>Dokument erfolgreich unterschrieben</h1>
|
|
||||||
<p class="text-muted">
|
|
||||||
Sie erhalten eine Bestätigung per E-Mail, sobald alle Empfänger unterschrieben haben.
|
|
||||||
</p>
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "rejected":
|
|
||||||
<div class="status-icon rejected">
|
|
||||||
<i class="bi bi-x-circle"></i>
|
|
||||||
</div>
|
|
||||||
<h1>Dokument wurde abgelehnt</h1>
|
|
||||||
<p class="text-muted">
|
|
||||||
@if (!string.IsNullOrEmpty(Title) && !string.IsNullOrEmpty(SenderEmail))
|
|
||||||
{
|
|
||||||
<span>
|
|
||||||
Das Dokument «@Title» wurde abgelehnt.
|
|
||||||
Bei Fragen wenden Sie sich an
|
|
||||||
<a href="mailto:@SenderEmail">@SenderEmail</a>.
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span>Dieses Dokument wurde von einem Empfänger abgelehnt.</span>
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "not_found":
|
|
||||||
<div class="status-icon locked">
|
|
||||||
<i class="bi bi-question-circle"></i>
|
|
||||||
</div>
|
|
||||||
<h1>Dokument nicht gefunden</h1>
|
|
||||||
<p class="text-muted">
|
|
||||||
Dieses Dokument existiert nicht oder ist nicht mehr verfügbar.
|
|
||||||
Wenn Sie diese URL per E-Mail erhalten haben, wenden Sie sich bitte an das IT-Team.
|
|
||||||
</p>
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "expired":
|
|
||||||
<div class="status-icon locked">
|
|
||||||
<i class="bi bi-clock-history"></i>
|
|
||||||
</div>
|
|
||||||
<h1>Link abgelaufen</h1>
|
|
||||||
<p class="text-muted">
|
|
||||||
Der Zugang zu diesem Dokument ist abgelaufen.
|
|
||||||
</p>
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
</header>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
/// <summary>
|
|
||||||
/// Bestimmt welche Status-Variante angezeigt wird.
|
|
||||||
/// Erlaubte Werte: "signed", "rejected", "not_found", "expired"
|
|
||||||
/// </summary>
|
|
||||||
[Parameter, EditorRequired]
|
|
||||||
public string Type { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
/// <summary>E-Mail des Absenders — nur bei "rejected" relevant.</summary>
|
|
||||||
[Parameter]
|
|
||||||
public string? SenderEmail { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Titel des Umschlags — nur bei "rejected" relevant.</summary>
|
|
||||||
[Parameter]
|
|
||||||
public string? Title { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
@* Toast: Zeigt kurze Benachrichtigungen an. Ersetzt AlertifyJS aus dem Web-Projekt.
|
|
||||||
|
|
||||||
Wird einmal im MainLayout platziert. Hört auf den ToastService.
|
|
||||||
Wenn eine Komponente irgendwo _toast.ShowSuccess("Text") aufruft,
|
|
||||||
erscheint hier ein Toast und verschwindet nach 4 Sekunden automatisch.
|
|
||||||
|
|
||||||
Bootstrap Toast-Klassen werden genutzt:
|
|
||||||
- toast-container: Positionierung (oben rechts)
|
|
||||||
- toast: Einzelner Toast
|
|
||||||
- bg-success/bg-danger/etc: Farbe basierend auf Typ *@
|
|
||||||
|
|
||||||
@inject ToastService ToastService
|
|
||||||
@implements IDisposable
|
|
||||||
|
|
||||||
@if (ToastService.Messages.Count > 0)
|
|
||||||
{
|
|
||||||
@* toast-container: Bootstrap-Klasse die Toasts oben rechts positioniert.
|
|
||||||
position-fixed: Bleibt an der Stelle auch beim Scrollen.
|
|
||||||
z-index 1080: Über dem Modal-Backdrop (1070) damit Toasts
|
|
||||||
auch während eines ConfirmDialogs sichtbar sind. *@
|
|
||||||
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 1080;">
|
|
||||||
@foreach (var message in ToastService.Messages)
|
|
||||||
{
|
|
||||||
<div class="toast show align-items-center text-white bg-@message.Type border-0"
|
|
||||||
role="alert">
|
|
||||||
<div class="d-flex">
|
|
||||||
<div class="toast-body">
|
|
||||||
<i class="bi @message.IconClass me-2"></i>
|
|
||||||
@message.Text
|
|
||||||
</div>
|
|
||||||
<button type="button"
|
|
||||||
class="btn-close btn-close-white me-2 m-auto"
|
|
||||||
@onclick="() => ToastService.Dismiss(message)">
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
// Subscriber: Wenn der ToastService eine Änderung meldet,
|
|
||||||
// zeichnet diese Komponente sich neu (StateHasChanged).
|
|
||||||
// So erscheinen/verschwinden Toasts automatisch.
|
|
||||||
ToastService.OnChange += HandleChange;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// InvokeAsync ist nötig weil OnChange von einem
|
|
||||||
/// async void (dem Timer im ToastService) ausgelöst wird.
|
|
||||||
/// Das kann auf einem anderen Thread passieren.
|
|
||||||
/// InvokeAsync wechselt zurück auf den Blazor UI-Thread.
|
|
||||||
/// </summary>
|
|
||||||
private async void HandleChange()
|
|
||||||
{
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Event-Handler abmelden wenn die Komponente entfernt wird.
|
|
||||||
/// Ohne das würde der ToastService eine Referenz auf eine
|
|
||||||
/// nicht mehr existierende Komponente halten → Memory Leak.
|
|
||||||
/// </summary>
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
ToastService.OnChange -= HandleChange;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
|
|
||||||
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.3" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.3" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.3" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Hält den aktuellen Authentifizierungs-Zustand im Client.
|
|
||||||
/// Wird vom ApiAuthStateProvider gesetzt und von Komponenten gelesen.
|
|
||||||
/// </summary>
|
|
||||||
public class AuthState
|
|
||||||
{
|
|
||||||
public bool IsAuthenticated { get; set; }
|
|
||||||
public string? Role { get; set; }
|
|
||||||
public string? EnvelopeUuid { get; set; }
|
|
||||||
public string? ReceiverEmail { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Client-seitiges DTO für Dokument-Daten.
|
|
||||||
/// </summary>
|
|
||||||
public record DocumentModel
|
|
||||||
{
|
|
||||||
public int Id { get; init; }
|
|
||||||
public int EnvelopeId { get; init; }
|
|
||||||
public DateTime AddedWhen { get; init; }
|
|
||||||
public byte[]? ByteData { get; init; }
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Client-seitiges DTO für Umschlag-Daten.
|
|
||||||
/// Muss nur die JSON-Properties matchen, die die API zurückgibt
|
|
||||||
/// und die der Client tatsächlich braucht.
|
|
||||||
///
|
|
||||||
/// WARUM eigene DTOs statt die aus EnvelopeGenerator.Application?
|
|
||||||
/// - Application hat Server-Abhängigkeiten (SqlClient, JwtBearer, EF Core)
|
|
||||||
/// - Diese Pakete existieren nicht für browser-wasm → Build-Fehler
|
|
||||||
/// - Der Client braucht nur eine Teilmenge der Felder
|
|
||||||
/// - Eigene DTOs machen den Client unabhängig vom Server
|
|
||||||
/// </summary>
|
|
||||||
public record EnvelopeModel
|
|
||||||
{
|
|
||||||
public int Id { get; init; }
|
|
||||||
public string Uuid { get; init; } = string.Empty;
|
|
||||||
public string Title { get; init; } = string.Empty;
|
|
||||||
public string Message { get; init; } = string.Empty;
|
|
||||||
public bool UseAccessCode { get; init; }
|
|
||||||
public bool TFAEnabled { get; init; }
|
|
||||||
public bool ReadOnly { get; init; }
|
|
||||||
public string Language { get; init; } = "de-DE";
|
|
||||||
public DateTime AddedWhen { get; init; }
|
|
||||||
public UserModel? User { get; init; }
|
|
||||||
public IEnumerable<DocumentModel>? Documents { get; init; }
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Client-seitiges DTO für die Envelope-Receiver-Zuordnung.
|
|
||||||
/// </summary>
|
|
||||||
public record EnvelopeReceiverModel
|
|
||||||
{
|
|
||||||
public EnvelopeModel? Envelope { get; init; }
|
|
||||||
public ReceiverModel? Receiver { get; init; }
|
|
||||||
public int EnvelopeId { get; init; }
|
|
||||||
public int ReceiverId { get; init; }
|
|
||||||
public int Sequence { get; init; }
|
|
||||||
public string? Name { get; init; }
|
|
||||||
public bool HasPhoneNumber { get; init; }
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Models
|
|
||||||
{
|
|
||||||
public class EnvelopeViewModel
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Client-seitiges DTO für die Antwort des ReceiverAuthControllers.
|
|
||||||
/// Wird 1:1 aus dem JSON deserialisiert.
|
|
||||||
///
|
|
||||||
/// WARUM ein eigenes Client-Model statt das API-Model zu referenzieren?
|
|
||||||
/// - Das API-Projekt hat Server-Abhängigkeiten (EF Core, SqlClient, etc.)
|
|
||||||
/// - Diese Pakete existieren nicht für browser-wasm → Build-Fehler
|
|
||||||
/// - Die Property-Namen müssen nur zum JSON passen (case-insensitive)
|
|
||||||
/// </summary>
|
|
||||||
public record ReceiverAuthModel
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Aktueller Status des Empfänger-Flows.
|
|
||||||
/// Werte: "requires_access_code", "requires_tfa", "show_document",
|
|
||||||
/// "already_signed", "rejected", "not_found", "expired", "error"
|
|
||||||
/// </summary>
|
|
||||||
public string Status { get; init; } = string.Empty;
|
|
||||||
|
|
||||||
public string? Title { get; init; }
|
|
||||||
public string? Message { get; init; }
|
|
||||||
public string? SenderEmail { get; init; }
|
|
||||||
public string? ReceiverName { get; init; }
|
|
||||||
public bool TfaEnabled { get; init; }
|
|
||||||
public bool HasPhoneNumber { get; init; }
|
|
||||||
public bool ReadOnly { get; init; }
|
|
||||||
public string? TfaType { get; init; }
|
|
||||||
public DateTime? TfaExpiration { get; init; }
|
|
||||||
public string? ErrorMessage { get; init; }
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Client-seitiges DTO für Empfänger-Daten.
|
|
||||||
/// </summary>
|
|
||||||
public record ReceiverModel
|
|
||||||
{
|
|
||||||
public int Id { get; init; }
|
|
||||||
public string EmailAddress { get; init; } = string.Empty;
|
|
||||||
public string Signature { get; init; } = string.Empty;
|
|
||||||
public DateTime AddedWhen { get; init; }
|
|
||||||
public DateTime? TfaRegDeadline { get; init; }
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Models;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Client-seitiges DTO für Benutzer-Daten (Absender).
|
|
||||||
/// </summary>
|
|
||||||
public record UserModel
|
|
||||||
{
|
|
||||||
public string? Email { get; init; }
|
|
||||||
public string? DisplayName { get; init; }
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<h3>EnvelopeExpired</h3>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<h3>EnvelopeLocked</h3>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
@page "/envelope/{EnvelopeKey}"
|
|
||||||
@rendermode InteractiveAuto
|
|
||||||
@inject IReceiverAuthService ReceiverAuthService
|
|
||||||
@inject EnvelopeState State
|
|
||||||
@implements IDisposable
|
|
||||||
|
|
||||||
<PageTitle>Dokument</PageTitle>
|
|
||||||
|
|
||||||
@switch (State.Status)
|
|
||||||
{
|
|
||||||
case EnvelopePageStatus.Loading:
|
|
||||||
<LoadingIndicator Message="Dokument wird geladen..." />
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EnvelopePageStatus.NotFound:
|
|
||||||
<StatusPage Type="not_found" />
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EnvelopePageStatus.AlreadySigned:
|
|
||||||
<StatusPage Type="signed"
|
|
||||||
Title="@State.Title"
|
|
||||||
SenderEmail="@State.SenderEmail" />
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EnvelopePageStatus.Rejected:
|
|
||||||
<StatusPage Type="rejected"
|
|
||||||
Title="@State.Title"
|
|
||||||
SenderEmail="@State.SenderEmail" />
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EnvelopePageStatus.Expired:
|
|
||||||
<StatusPage Type="expired" />
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EnvelopePageStatus.RequiresAccessCode:
|
|
||||||
<AccessCodeForm EnvelopeKey="@EnvelopeKey"
|
|
||||||
ErrorMessage="@State.ErrorMessage"
|
|
||||||
SenderEmail="@State.SenderEmail"
|
|
||||||
Title="@State.Title"
|
|
||||||
TfaEnabled="@State.TfaEnabled"
|
|
||||||
HasPhoneNumber="@State.HasPhoneNumber"
|
|
||||||
OnSubmit="HandleAccessCodeSubmit" />
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EnvelopePageStatus.RequiresTwoFactor:
|
|
||||||
<TfaForm EnvelopeKey="@EnvelopeKey"
|
|
||||||
TfaType="@(State.TfaType ?? "authenticator")"
|
|
||||||
TfaExpiration="@State.TfaExpiration"
|
|
||||||
HasPhoneNumber="@State.HasPhoneNumber"
|
|
||||||
ErrorMessage="@State.ErrorMessage"
|
|
||||||
OnSubmit="HandleTfaSubmit" />
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EnvelopePageStatus.ShowDocument:
|
|
||||||
@* Phase 4 (PSPDFKit) kommt später — vorerst Platzhalter *@
|
|
||||||
<div class="text-center mt-5">
|
|
||||||
<div class="status-icon signed">
|
|
||||||
<i class="bi bi-file-earmark-check"></i>
|
|
||||||
</div>
|
|
||||||
<h2>Dokument bereit</h2>
|
|
||||||
<p class="text-muted">
|
|
||||||
«@State.Title» — PDF-Viewer wird in Phase 4 integriert.
|
|
||||||
</p>
|
|
||||||
@if (State.ReadOnly)
|
|
||||||
{
|
|
||||||
<span class="badge bg-secondary">Nur Lesen</span>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EnvelopePageStatus.Error:
|
|
||||||
<ErrorDisplay Title="Fehler" Message="@State.ErrorMessage" />
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter] public string EnvelopeKey { get; set; } = default!;
|
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
|
||||||
{
|
|
||||||
State.OnChange += StateHasChanged;
|
|
||||||
await LoadStatusAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Erster API-Call: Status prüfen.
|
|
||||||
/// Entspricht dem GET /Envelope/{key} im Web-Projekt.
|
|
||||||
/// Die API entscheidet, was passiert (AccessCode nötig? Bereits signiert? etc.)
|
|
||||||
/// </summary>
|
|
||||||
private async Task LoadStatusAsync()
|
|
||||||
{
|
|
||||||
State.SetLoading();
|
|
||||||
|
|
||||||
var result = await ReceiverAuthService.GetStatusAsync(EnvelopeKey);
|
|
||||||
|
|
||||||
if (result.IsSuccess && result.Data is not null)
|
|
||||||
{
|
|
||||||
State.ApplyApiResponse(result.Data);
|
|
||||||
}
|
|
||||||
else if (result.StatusCode == 404)
|
|
||||||
{
|
|
||||||
State.SetNotFound();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
State.SetError(result.ErrorMessage ?? "Verbindung zum Server fehlgeschlagen.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Zweiter API-Call: AccessCode senden.
|
|
||||||
/// Wird von AccessCodeForm aufgerufen (OnSubmit-Callback).
|
|
||||||
/// Die API prüft den Code und antwortet mit dem nächsten Status.
|
|
||||||
/// </summary>
|
|
||||||
private async Task HandleAccessCodeSubmit((string Code, bool PreferSms) submission)
|
|
||||||
{
|
|
||||||
var result = await ReceiverAuthService.SubmitAccessCodeAsync(
|
|
||||||
EnvelopeKey, submission.Code, submission.PreferSms);
|
|
||||||
|
|
||||||
if (result.IsSuccess && result.Data is not null)
|
|
||||||
{
|
|
||||||
State.ApplyApiResponse(result.Data);
|
|
||||||
}
|
|
||||||
else if (result.Data is not null)
|
|
||||||
{
|
|
||||||
// 401 mit Body → falscher Code, API gibt trotzdem ReceiverAuthModel zurück
|
|
||||||
State.ApplyApiResponse(result.Data);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
State.SetError(result.ErrorMessage ?? "Fehler bei der Code-Prüfung.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Dritter API-Call: TFA-Code senden.
|
|
||||||
/// Wird von TfaForm aufgerufen (OnSubmit-Callback).
|
|
||||||
/// </summary>
|
|
||||||
private async Task HandleTfaSubmit((string Code, string Type) submission)
|
|
||||||
{
|
|
||||||
var result = await ReceiverAuthService.SubmitTfaCodeAsync(
|
|
||||||
EnvelopeKey, submission.Code, submission.Type);
|
|
||||||
|
|
||||||
if (result.IsSuccess && result.Data is not null)
|
|
||||||
{
|
|
||||||
State.ApplyApiResponse(result.Data);
|
|
||||||
}
|
|
||||||
else if (result.Data is not null)
|
|
||||||
{
|
|
||||||
State.ApplyApiResponse(result.Data);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
State.SetError(result.ErrorMessage ?? "Fehler bei der TFA-Prüfung.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose() => State.OnChange -= StateHasChanged;
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<h3>EnvelopeRejected</h3>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<h3>EnvelopeSigned</h3>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<h3>Home</h3>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<h3>NotFound</h3>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using Microsoft.AspNetCore.Components.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
|
||||||
using EnvelopeGenerator.ReceiverUI.Client.Auth;
|
|
||||||
using EnvelopeGenerator.ReceiverUI.Client.Services;
|
|
||||||
using EnvelopeGenerator.ReceiverUI.Client.State;
|
|
||||||
|
|
||||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
|
||||||
|
|
||||||
// HttpClient: BaseAddress zeigt auf den ReceiverUI-Server (gleiche Domain)
|
|
||||||
// Von dort werden alle /api/* Calls via YARP an die echte API weitergeleitet
|
|
||||||
builder.Services.AddScoped(sp =>
|
|
||||||
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
|
||||||
|
|
||||||
// Auth: Blazor fragt über diesen Provider "Ist der Nutzer eingeloggt?"
|
|
||||||
builder.Services.AddAuthorizationCore();
|
|
||||||
builder.Services.AddScoped<ApiAuthStateProvider>();
|
|
||||||
builder.Services.AddScoped<AuthenticationStateProvider>(sp =>
|
|
||||||
sp.GetRequiredService<ApiAuthStateProvider>());
|
|
||||||
|
|
||||||
// API-Services: Je ein Service pro API-Controller
|
|
||||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
|
||||||
builder.Services.AddScoped<IEnvelopeService, EnvelopeService>();
|
|
||||||
builder.Services.AddScoped<IReceiverAuthService, ReceiverAuthService>();
|
|
||||||
|
|
||||||
// State: Ein State-Objekt pro Browser-Tab
|
|
||||||
builder.Services.AddScoped<EnvelopeState>();
|
|
||||||
|
|
||||||
builder.Services.AddScoped<ToastService>();
|
|
||||||
|
|
||||||
await builder.Build().RunAsync();
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
using EnvelopeGenerator.ReceiverUI.Client.Services.Base;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Spricht mit dem bestehenden AuthController der API.
|
|
||||||
/// Die API erkennt den Nutzer über das Cookie "AuthToken" automatisch.
|
|
||||||
/// </summary>
|
|
||||||
public class AuthService : ApiServiceBase, IAuthService
|
|
||||||
{
|
|
||||||
public AuthService(HttpClient http, ILogger<AuthService> logger) : base(http, logger) { }
|
|
||||||
|
|
||||||
public async Task<ApiResponse> CheckAuthAsync(string? role = null, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
var endpoint = role is not null ? $"api/auth/check?role={role}" : "api/auth/check";
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await Http.GetAsync(endpoint, ct);
|
|
||||||
return response.IsSuccessStatusCode
|
|
||||||
? ApiResponse.Success((int)response.StatusCode)
|
|
||||||
: ApiResponse.Failure((int)response.StatusCode);
|
|
||||||
}
|
|
||||||
catch (HttpRequestException ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "HTTP error calling GET {Endpoint}", endpoint);
|
|
||||||
return ApiResponse.Failure(0, "Verbindung zum Server fehlgeschlagen.");
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException)
|
|
||||||
{
|
|
||||||
return ApiResponse.Failure(0, "Anfrage abgebrochen.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ApiResponse> LogoutAsync(CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
const string endpoint = "api/auth/logout";
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await Http.PostAsync(endpoint, null, ct);
|
|
||||||
return response.IsSuccessStatusCode
|
|
||||||
? ApiResponse.Success((int)response.StatusCode)
|
|
||||||
: ApiResponse.Failure((int)response.StatusCode);
|
|
||||||
}
|
|
||||||
catch (HttpRequestException ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "HTTP error calling POST {Endpoint}", endpoint);
|
|
||||||
return ApiResponse.Failure(0, "Verbindung zum Server fehlgeschlagen.");
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException)
|
|
||||||
{
|
|
||||||
return ApiResponse.Failure(0, "Anfrage abgebrochen.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Services.Base;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Einheitliches Response-Objekt für ALLE API-Aufrufe.
|
|
||||||
///
|
|
||||||
/// WARUM: Jeder API-Aufruf kann fehlschlagen (Netzwerk, 401, 500...).
|
|
||||||
/// Statt überall try-catch zu haben, kapselt dieses Objekt Erfolg/Fehler einheitlich.
|
|
||||||
/// So kann jede Blazor-Komponente einheitlich darauf reagieren.
|
|
||||||
/// </summary>
|
|
||||||
public record ApiResponse<T>
|
|
||||||
{
|
|
||||||
public bool IsSuccess { get; init; }
|
|
||||||
public T? Data { get; init; }
|
|
||||||
public int StatusCode { get; init; }
|
|
||||||
public string? ErrorMessage { get; init; }
|
|
||||||
|
|
||||||
public static ApiResponse<T> Success(T data, int statusCode = 200)
|
|
||||||
=> new() { IsSuccess = true, Data = data, StatusCode = statusCode };
|
|
||||||
|
|
||||||
public static ApiResponse<T> Failure(int statusCode, string? error = null)
|
|
||||||
=> new() { IsSuccess = false, StatusCode = statusCode, ErrorMessage = error };
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Failure mit deserialisiertem Body — für Fälle wo die API
|
|
||||||
/// bei 401/404 trotzdem ein strukturiertes JSON zurückgibt
|
|
||||||
/// (z.B. ReceiverAuthResponse mit ErrorMessage + Status).
|
|
||||||
/// </summary>
|
|
||||||
public static ApiResponse<T> Failure(int statusCode, string? error, T? data)
|
|
||||||
=> new() { IsSuccess = false, StatusCode = statusCode, ErrorMessage = error, Data = data };
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Response ohne Daten (für POST/PUT/DELETE die nur Status zurückgeben).
|
|
||||||
/// </summary>
|
|
||||||
public record ApiResponse
|
|
||||||
{
|
|
||||||
public bool IsSuccess { get; init; }
|
|
||||||
public int StatusCode { get; init; }
|
|
||||||
public string? ErrorMessage { get; init; }
|
|
||||||
|
|
||||||
public static ApiResponse Success(int statusCode = 200)
|
|
||||||
=> new() { IsSuccess = true, StatusCode = statusCode };
|
|
||||||
|
|
||||||
public static ApiResponse Failure(int statusCode, string? error = null)
|
|
||||||
=> new() { IsSuccess = false, StatusCode = statusCode, ErrorMessage = error };
|
|
||||||
}
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
using System.Net.Http.Json;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Services.Base;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Basisklasse für ALLE API-Services.
|
|
||||||
///
|
|
||||||
/// WARUM eine Basisklasse?
|
|
||||||
/// - Einheitliches Error-Handling: Jeder API-Aufruf wird gleich behandelt
|
|
||||||
/// - DRY (Don't Repeat Yourself): Logging, Fehlerbehandlung, Serialisierung nur einmal
|
|
||||||
/// - Einfache Erweiterung: Retry-Logik, Token-Refresh etc. nur hier ändern
|
|
||||||
/// </summary>
|
|
||||||
public abstract class ApiServiceBase
|
|
||||||
{
|
|
||||||
protected readonly HttpClient Http;
|
|
||||||
protected readonly ILogger Logger;
|
|
||||||
|
|
||||||
protected ApiServiceBase(HttpClient http, ILogger logger)
|
|
||||||
{
|
|
||||||
Http = http;
|
|
||||||
Logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// GET-Request mit Deserialisierung.
|
|
||||||
/// Alle API GET-Aufrufe gehen durch diese Methode.
|
|
||||||
/// </summary>
|
|
||||||
protected async Task<ApiResponse<T>> GetAsync<T>(string endpoint, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await Http.GetAsync(endpoint, ct);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var errorBody = await response.Content.ReadAsStringAsync(ct);
|
|
||||||
Logger.LogWarning("GET {Endpoint} failed: {Status} - {Body}",
|
|
||||||
endpoint, (int)response.StatusCode, errorBody);
|
|
||||||
|
|
||||||
// Versuche den Body trotzdem zu deserialisieren —
|
|
||||||
// die API gibt bei 401/404 oft strukturierte JSON-Antworten zurück
|
|
||||||
// (z.B. ReceiverAuthResponse mit ErrorMessage + Status)
|
|
||||||
var errorData = await TryDeserializeAsync<T>(response, ct);
|
|
||||||
|
|
||||||
return ApiResponse<T>.Failure((int)response.StatusCode, errorBody, errorData);
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = await response.Content.ReadFromJsonAsync<T>(cancellationToken: ct);
|
|
||||||
return ApiResponse<T>.Success(data!, (int)response.StatusCode);
|
|
||||||
}
|
|
||||||
catch (HttpRequestException ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "HTTP error calling GET {Endpoint}", endpoint);
|
|
||||||
return ApiResponse<T>.Failure(0, "Verbindung zum Server fehlgeschlagen.");
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException)
|
|
||||||
{
|
|
||||||
Logger.LogWarning("GET {Endpoint} was cancelled", endpoint);
|
|
||||||
return ApiResponse<T>.Failure(0, "Anfrage abgebrochen.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// POST-Request mit Body und Response-Deserialisierung.
|
|
||||||
/// </summary>
|
|
||||||
protected async Task<ApiResponse<TResponse>> PostAsync<TRequest, TResponse>(
|
|
||||||
string endpoint, TRequest body, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await Http.PostAsJsonAsync(endpoint, body, ct);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var errorBody = await response.Content.ReadAsStringAsync(ct);
|
|
||||||
Logger.LogWarning("POST {Endpoint} failed: {Status} - {Body}",
|
|
||||||
endpoint, (int)response.StatusCode, errorBody);
|
|
||||||
|
|
||||||
var errorData = await TryDeserializeAsync<TResponse>(response, ct);
|
|
||||||
|
|
||||||
return ApiResponse<TResponse>.Failure((int)response.StatusCode, errorBody, errorData);
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = await response.Content.ReadFromJsonAsync<TResponse>(cancellationToken: ct);
|
|
||||||
return ApiResponse<TResponse>.Success(data!, (int)response.StatusCode);
|
|
||||||
}
|
|
||||||
catch (HttpRequestException ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "HTTP error calling POST {Endpoint}", endpoint);
|
|
||||||
return ApiResponse<TResponse>.Failure(0, "Verbindung zum Server fehlgeschlagen.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// POST-Request ohne Response-Body (z.B. Logout).
|
|
||||||
/// </summary>
|
|
||||||
protected async Task<ApiResponse> PostAsync<TRequest>(
|
|
||||||
string endpoint, TRequest body, CancellationToken ct = default)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var response = await Http.PostAsJsonAsync(endpoint, body, ct);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
|
||||||
{
|
|
||||||
var errorBody = await response.Content.ReadAsStringAsync(ct);
|
|
||||||
return ApiResponse.Failure((int)response.StatusCode, errorBody);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ApiResponse.Success((int)response.StatusCode);
|
|
||||||
}
|
|
||||||
catch (HttpRequestException ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "HTTP error calling POST {Endpoint}", endpoint);
|
|
||||||
return ApiResponse.Failure(0, "Verbindung zum Server fehlgeschlagen.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Versucht den Response-Body als JSON zu deserialisieren.
|
|
||||||
/// Gibt null zurück wenn es nicht klappt (z.B. bei HTML-Fehlerseiten).
|
|
||||||
/// </summary>
|
|
||||||
private static async Task<T?> TryDeserializeAsync<T>(HttpResponseMessage response, CancellationToken ct)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Nur versuchen wenn der Content-Type JSON ist
|
|
||||||
if (response.Content.Headers.ContentType?.MediaType == "application/json")
|
|
||||||
{
|
|
||||||
return await response.Content.ReadFromJsonAsync<T>(cancellationToken: ct);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Ignorieren — der Body war kein valides JSON
|
|
||||||
}
|
|
||||||
|
|
||||||
return default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using EnvelopeGenerator.ReceiverUI.Client.Models;
|
|
||||||
using EnvelopeGenerator.ReceiverUI.Client.Services.Base;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Services;
|
|
||||||
|
|
||||||
public class EnvelopeService : ApiServiceBase, IEnvelopeService
|
|
||||||
{
|
|
||||||
public EnvelopeService(HttpClient http, ILogger<EnvelopeService> logger) : base(http, logger) { }
|
|
||||||
|
|
||||||
public Task<ApiResponse<IEnumerable<EnvelopeModel>>> GetEnvelopesAsync(CancellationToken ct = default)
|
|
||||||
=> GetAsync<IEnumerable<EnvelopeModel>>("api/envelope", ct);
|
|
||||||
|
|
||||||
public Task<ApiResponse<IEnumerable<EnvelopeReceiverModel>>> GetEnvelopeReceiversAsync(
|
|
||||||
CancellationToken ct = default)
|
|
||||||
=> GetAsync<IEnumerable<EnvelopeReceiverModel>>("api/envelopereceiver", ct);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Services
|
|
||||||
{
|
|
||||||
public class HistoryService
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using EnvelopeGenerator.ReceiverUI.Client.Services.Base;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Kommuniziert mit dem AuthController der API.
|
|
||||||
///
|
|
||||||
/// WARUM Interface + Implementierung?
|
|
||||||
/// - Testbarkeit: In Unit-Tests kann man einen Mock verwenden
|
|
||||||
/// - Austauschbarkeit: Wenn sich die API ändert, ändert sich nur die Implementierung
|
|
||||||
/// - Blazor-Konvention: Services werden über Interfaces per DI registriert
|
|
||||||
/// </summary>
|
|
||||||
public interface IAuthService
|
|
||||||
{
|
|
||||||
/// <summary>Prüft ob der Nutzer eingeloggt ist → GET /api/auth/check</summary>
|
|
||||||
Task<ApiResponse> CheckAuthAsync(string? role = null, CancellationToken ct = default);
|
|
||||||
|
|
||||||
/// <summary>Logout → POST /api/auth/logout</summary>
|
|
||||||
Task<ApiResponse> LogoutAsync(CancellationToken ct = default);
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using EnvelopeGenerator.ReceiverUI.Client.Models;
|
|
||||||
using EnvelopeGenerator.ReceiverUI.Client.Services.Base;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Kommuniziert mit EnvelopeController und EnvelopeReceiverController.
|
|
||||||
/// Verwendet Client-eigene Models statt der Server-DTOs.
|
|
||||||
/// </summary>
|
|
||||||
public interface IEnvelopeService
|
|
||||||
{
|
|
||||||
/// <summary>Lädt Umschläge → GET /api/envelope</summary>
|
|
||||||
Task<ApiResponse<IEnumerable<EnvelopeModel>>> GetEnvelopesAsync(CancellationToken ct = default);
|
|
||||||
|
|
||||||
/// <summary>Lädt EnvelopeReceiver → GET /api/envelopereceiver</summary>
|
|
||||||
Task<ApiResponse<IEnumerable<EnvelopeReceiverModel>>> GetEnvelopeReceiversAsync(
|
|
||||||
CancellationToken ct = default);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Services
|
|
||||||
{
|
|
||||||
public interface IHistoryService
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
using EnvelopeGenerator.ReceiverUI.Client.Models;
|
|
||||||
using EnvelopeGenerator.ReceiverUI.Client.Services.Base;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Kommuniziert mit dem ReceiverAuthController der API.
|
|
||||||
///
|
|
||||||
/// Drei Methoden — eine pro Endpunkt:
|
|
||||||
/// 1. GetStatusAsync → GET /api/receiverauth/{key}/status
|
|
||||||
/// 2. SubmitAccessCodeAsync → POST /api/receiverauth/{key}/access-code
|
|
||||||
/// 3. SubmitTfaCodeAsync → POST /api/receiverauth/{key}/tfa
|
|
||||||
/// </summary>
|
|
||||||
public interface IReceiverAuthService
|
|
||||||
{
|
|
||||||
/// <summary>Prüft den aktuellen Status des Empfänger-Flows</summary>
|
|
||||||
Task<ApiResponse<ReceiverAuthModel>> GetStatusAsync(string key, CancellationToken ct = default);
|
|
||||||
|
|
||||||
/// <summary>Sendet den Zugangscode zur Prüfung</summary>
|
|
||||||
Task<ApiResponse<ReceiverAuthModel>> SubmitAccessCodeAsync(
|
|
||||||
string key, string accessCode, bool preferSms, CancellationToken ct = default);
|
|
||||||
|
|
||||||
/// <summary>Sendet den TFA-Code (SMS oder Authenticator) zur Prüfung</summary>
|
|
||||||
Task<ApiResponse<ReceiverAuthModel>> SubmitTfaCodeAsync(
|
|
||||||
string key, string code, string type, CancellationToken ct = default);
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
using EnvelopeGenerator.ReceiverUI.Client.Models;
|
|
||||||
using EnvelopeGenerator.ReceiverUI.Client.Services.Base;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Spricht mit dem ReceiverAuthController der API.
|
|
||||||
///
|
|
||||||
/// Nutzt die Basisklasse ApiServiceBase für einheitliches Error-Handling.
|
|
||||||
/// Jede Methode gibt ApiResponse<ReceiverAuthModel> zurück —
|
|
||||||
/// egal ob Erfolg oder Fehler. Die aufrufende Komponente prüft dann
|
|
||||||
/// result.IsSuccess und result.Data.Status.
|
|
||||||
///
|
|
||||||
/// WARUM gibt die API bei 401 trotzdem ein ReceiverAuthModel zurück?
|
|
||||||
/// Weil auch bei "falscher Code" der Client wissen muss, welchen
|
|
||||||
/// Status er anzeigen soll (z.B. "requires_access_code" + ErrorMessage).
|
|
||||||
/// Deshalb deserialisieren wir auch bei Fehler-Statuscodes den Body.
|
|
||||||
/// </summary>
|
|
||||||
public class ReceiverAuthService : ApiServiceBase, IReceiverAuthService
|
|
||||||
{
|
|
||||||
public ReceiverAuthService(HttpClient http, ILogger<ReceiverAuthService> logger)
|
|
||||||
: base(http, logger) { }
|
|
||||||
|
|
||||||
public Task<ApiResponse<ReceiverAuthModel>> GetStatusAsync(
|
|
||||||
string key, CancellationToken ct = default)
|
|
||||||
=> GetAsync<ReceiverAuthModel>($"api/receiverauth/{key}/status", ct);
|
|
||||||
|
|
||||||
public Task<ApiResponse<ReceiverAuthModel>> SubmitAccessCodeAsync(
|
|
||||||
string key, string accessCode, bool preferSms, CancellationToken ct = default)
|
|
||||||
=> PostAsync<object, ReceiverAuthModel>(
|
|
||||||
$"api/receiverauth/{key}/access-code",
|
|
||||||
new { AccessCode = accessCode, PreferSms = preferSms },
|
|
||||||
ct);
|
|
||||||
|
|
||||||
public Task<ApiResponse<ReceiverAuthModel>> SubmitTfaCodeAsync(
|
|
||||||
string key, string code, string type, CancellationToken ct = default)
|
|
||||||
=> PostAsync<object, ReceiverAuthModel>(
|
|
||||||
$"api/receiverauth/{key}/tfa",
|
|
||||||
new { Code = code, Type = type },
|
|
||||||
ct);
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.ReceiverUI.Client.Services;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Service für Toast-Benachrichtigungen. Ersetzt AlertifyJS aus dem Web-Projekt.
|
|
||||||
///
|
|
||||||
/// WARUM ein Service und keine Komponente mit Parametern?
|
|
||||||
/// - Toasts können von ÜBERALL ausgelöst werden (Pages, Services, andere Komponenten)
|
|
||||||
/// - Ein Service ist über Dependency Injection überall verfügbar
|
|
||||||
/// - Die Toast-Komponente im Layout hört auf diesen Service und rendert die Nachrichten
|
|
||||||
///
|
|
||||||
/// PATTERN: Pub/Sub (Publisher/Subscriber)
|
|
||||||
/// - Publisher: Jede Komponente die _toast.ShowSuccess("...") aufruft
|
|
||||||
/// - Subscriber: Die Toast-Komponente im MainLayout, die auf OnChange hört
|
|
||||||
///
|
|
||||||
/// Das ist das gleiche Pattern wie beim EnvelopeState.
|
|
||||||
/// </summary>
|
|
||||||
public class ToastService
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Liste aller aktuell sichtbaren Toasts.
|
|
||||||
/// Mehrere Toasts können gleichzeitig angezeigt werden (gestapelt).
|
|
||||||
/// </summary>
|
|
||||||
public List<ToastMessage> Messages { get; } = [];
|
|
||||||
|
|
||||||
/// <summary>Event: Informiert die Toast-Komponente über Änderungen</summary>
|
|
||||||
public event Action? OnChange;
|
|
||||||
|
|
||||||
public void ShowSuccess(string text) => Show(text, "success");
|
|
||||||
public void ShowError(string text) => Show(text, "danger");
|
|
||||||
public void ShowInfo(string text) => Show(text, "info");
|
|
||||||
public void ShowWarning(string text) => Show(text, "warning");
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fügt einen Toast hinzu und entfernt ihn nach der angegebenen Dauer automatisch.
|
|
||||||
///
|
|
||||||
/// WARUM async void?
|
|
||||||
/// Normalerweise vermeidet man async void. Hier ist es ok, weil:
|
|
||||||
/// - Es ist ein Fire-and-Forget-Timer (wir warten nicht auf das Ergebnis)
|
|
||||||
/// - Fehler im Delay können die App nicht zum Absturz bringen
|
|
||||||
/// - Das ist ein gängiges Pattern für Auto-Dismiss-Logik
|
|
||||||
/// </summary>
|
|
||||||
private async void Show(string text, string type, int durationMs = 4000)
|
|
||||||
{
|
|
||||||
var message = new ToastMessage(text, type);
|
|
||||||
Messages.Add(message);
|
|
||||||
OnChange?.Invoke();
|
|
||||||
|
|
||||||
// Nach Ablauf der Dauer automatisch entfernen
|
|
||||||
await Task.Delay(durationMs);
|
|
||||||
Messages.Remove(message);
|
|
||||||
OnChange?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Entfernt einen Toast sofort (z.B. wenn der Benutzer auf X klickt)</summary>
|
|
||||||
public void Dismiss(ToastMessage message)
|
|
||||||
{
|
|
||||||
Messages.Remove(message);
|
|
||||||
OnChange?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Ein einzelner Toast-Eintrag.
|
|
||||||
///
|
|
||||||
/// WARUM ein record statt class?
|
|
||||||
/// - Records haben automatisch Equals/GetHashCode basierend auf allen Properties
|
|
||||||
/// - Wir brauchen das für Messages.Remove() — es vergleicht über Referenz-Gleichheit
|
|
||||||
/// - Die Id (Guid) macht jeden Toast einzigartig, auch bei gleichem Text
|
|
||||||
/// </summary>
|
|
||||||
public record ToastMessage(string Text, string Type)
|
|
||||||
{
|
|
||||||
/// <summary>Eindeutige Id — damit zwei Toasts mit gleichem Text unterscheidbar sind</summary>
|
|
||||||
public Guid Id { get; } = Guid.NewGuid();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gibt die Bootstrap-Icon-Klasse basierend auf dem Typ zurück.
|
|
||||||
/// success → check-circle, danger → x-circle, etc.
|
|
||||||
/// </summary>
|
|
||||||
public string IconClass => Type switch
|
|
||||||
{
|
|
||||||
"success" => "bi-check-circle-fill",
|
|
||||||
"danger" => "bi-x-circle-fill",
|
|
||||||
"warning" => "bi-exclamation-triangle-fill",
|
|
||||||
_ => "bi-info-circle-fill"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace EnvelopeGenerator.ReceiverUI.Client.State
|
|
||||||
{
|
|
||||||
public class AuthState
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
using EnvelopeGenerator.ReceiverUI.Client.Models;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.ReceiverUI.Client.State;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Hält den aktuellen Zustand des geladenen Umschlags.
|
|
||||||
///
|
|
||||||
/// WARUM ein eigenes State-Objekt?
|
|
||||||
/// - Mehrere Komponenten auf einer Seite brauchen die gleichen Daten
|
|
||||||
/// - Ohne State müsste jede Komponente die Daten selbst laden → doppelte API-Calls
|
|
||||||
/// - StateHasChanged() informiert automatisch alle Subscriber
|
|
||||||
///
|
|
||||||
/// PATTERN: "Observable State" — Services setzen den State, Komponenten reagieren darauf.
|
|
||||||
///
|
|
||||||
/// Die Set-Methoden nehmen jetzt ein ReceiverAuthModel entgegen,
|
|
||||||
/// damit alle Felder (Title, SenderEmail, TfaType etc.) zentral gespeichert werden.
|
|
||||||
/// </summary>
|
|
||||||
public class EnvelopeState
|
|
||||||
{
|
|
||||||
private EnvelopePageStatus _status = EnvelopePageStatus.Loading;
|
|
||||||
|
|
||||||
/// <summary>Aktueller Seitenstatus</summary>
|
|
||||||
public EnvelopePageStatus Status
|
|
||||||
{
|
|
||||||
get => _status;
|
|
||||||
private set
|
|
||||||
{
|
|
||||||
_status = value;
|
|
||||||
NotifyStateChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Felder aus ReceiverAuthModel ──
|
|
||||||
|
|
||||||
/// <summary>Titel des Umschlags (z.B. "Vertragsdokument")</summary>
|
|
||||||
public string? Title { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>Nachricht des Absenders</summary>
|
|
||||||
public string? Message { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>E-Mail des Absenders (für Rückfragen-Hinweis)</summary>
|
|
||||||
public string? SenderEmail { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>Ob TFA für diesen Umschlag aktiviert ist</summary>
|
|
||||||
public bool TfaEnabled { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>Ob der Empfänger eine Telefonnummer hat (für SMS-TFA)</summary>
|
|
||||||
public bool HasPhoneNumber { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>Ob das Dokument nur gelesen werden soll (ReadAndConfirm)</summary>
|
|
||||||
public bool ReadOnly { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>TFA-Typ: "sms" oder "authenticator"</summary>
|
|
||||||
public string? TfaType { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>Ablaufzeit des SMS-Codes (für Countdown-Timer)</summary>
|
|
||||||
public DateTime? TfaExpiration { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>Fehlermeldung (z.B. "Falscher Zugangscode")</summary>
|
|
||||||
public string? ErrorMessage { get; private set; }
|
|
||||||
|
|
||||||
// ── Zustandsübergänge ──
|
|
||||||
|
|
||||||
public void SetLoading()
|
|
||||||
{
|
|
||||||
ErrorMessage = null;
|
|
||||||
Status = EnvelopePageStatus.Loading;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Setzt den State aus einer API-Antwort.
|
|
||||||
/// Zentrale Methode — alle Endpunkte liefern ReceiverAuthModel,
|
|
||||||
/// und diese Methode mappt den Status-String auf das richtige Enum.
|
|
||||||
/// </summary>
|
|
||||||
public void ApplyApiResponse(ReceiverAuthModel model)
|
|
||||||
{
|
|
||||||
// Gemeinsame Felder immer übernehmen
|
|
||||||
Title = model.Title ?? Title;
|
|
||||||
Message = model.Message ?? Message;
|
|
||||||
SenderEmail = model.SenderEmail ?? SenderEmail;
|
|
||||||
TfaEnabled = model.TfaEnabled;
|
|
||||||
HasPhoneNumber = model.HasPhoneNumber;
|
|
||||||
ReadOnly = model.ReadOnly;
|
|
||||||
TfaType = model.TfaType ?? TfaType;
|
|
||||||
TfaExpiration = model.TfaExpiration ?? TfaExpiration;
|
|
||||||
ErrorMessage = model.ErrorMessage;
|
|
||||||
|
|
||||||
// Status-String → Enum
|
|
||||||
Status = model.Status switch
|
|
||||||
{
|
|
||||||
"requires_access_code" => EnvelopePageStatus.RequiresAccessCode,
|
|
||||||
"requires_tfa" => EnvelopePageStatus.RequiresTwoFactor,
|
|
||||||
"show_document" => EnvelopePageStatus.ShowDocument,
|
|
||||||
"already_signed" => EnvelopePageStatus.AlreadySigned,
|
|
||||||
"rejected" => EnvelopePageStatus.Rejected,
|
|
||||||
"not_found" => EnvelopePageStatus.NotFound,
|
|
||||||
"expired" => EnvelopePageStatus.Expired,
|
|
||||||
"error" => EnvelopePageStatus.Error,
|
|
||||||
_ => EnvelopePageStatus.Error
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Setzt Fehler wenn der API-Call selbst fehlschlägt (Netzwerk etc.)</summary>
|
|
||||||
public void SetError(string message)
|
|
||||||
{
|
|
||||||
ErrorMessage = message;
|
|
||||||
Status = EnvelopePageStatus.Error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Setzt NotFound (z.B. bei 404 ohne Body)</summary>
|
|
||||||
public void SetNotFound() => Status = EnvelopePageStatus.NotFound;
|
|
||||||
|
|
||||||
// ── Event ──
|
|
||||||
public event Action? OnChange;
|
|
||||||
private void NotifyStateChanged() => OnChange?.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Alle möglichen Zustände der Umschlag-Seite</summary>
|
|
||||||
public enum EnvelopePageStatus
|
|
||||||
{
|
|
||||||
Loading,
|
|
||||||
RequiresAccessCode,
|
|
||||||
RequiresTwoFactor,
|
|
||||||
ShowDocument,
|
|
||||||
AlreadySigned,
|
|
||||||
Rejected,
|
|
||||||
NotFound,
|
|
||||||
Expired,
|
|
||||||
Error
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
@using System.Net.Http
|
|
||||||
@using System.Net.Http.Json
|
|
||||||
@using Microsoft.AspNetCore.Components.Authorization
|
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
|
||||||
@using Microsoft.AspNetCore.Components.Web
|
|
||||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
|
||||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
|
||||||
@using Microsoft.JSInterop
|
|
||||||
@using EnvelopeGenerator.ReceiverUI.Client
|
|
||||||
@using EnvelopeGenerator.ReceiverUI.Client.Models
|
|
||||||
@using EnvelopeGenerator.ReceiverUI.Client.Services
|
|
||||||
@using EnvelopeGenerator.ReceiverUI.Client.Services.Base
|
|
||||||
@using EnvelopeGenerator.ReceiverUI.Client.State
|
|
||||||
@using EnvelopeGenerator.ReceiverUI.Client.Auth
|
|
||||||
@using EnvelopeGenerator.ReceiverUI.Client.Components.Shared
|
|
||||||
@using EnvelopeGenerator.ReceiverUI.Client.Components.Envelope
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
body {
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="de">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<base href="/" />
|
|
||||||
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
|
|
||||||
<link rel="stylesheet" href="bootstrap-icons/bootstrap-icons.min.css" />
|
|
||||||
<link rel="stylesheet" href="app.css" />
|
|
||||||
<link rel="stylesheet" href="EnvelopeGenerator.ReceiverUI.styles.css" />
|
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
|
||||||
<HeadOutlet />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<Routes />
|
|
||||||
<script src="_framework/blazor.web.js"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<h3>AuthLayout</h3>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
@* MainLayout: Das Grundgerüst jeder Seite.
|
|
||||||
Entspricht _Layout.cshtml im Web-Projekt.
|
|
||||||
|
|
||||||
Aufbau:
|
|
||||||
- Header: signFLOW-Logo/Titel (oben, sticky)
|
|
||||||
- Main: Der Seiteninhalt (@Body) mit ErrorBoundary
|
|
||||||
- Footer: Copyright + Privacy-Link (unten)
|
|
||||||
|
|
||||||
Sticky Footer Pattern: Der Footer klebt immer am unteren Rand,
|
|
||||||
auch wenn der Inhalt wenig Platz braucht. Das funktioniert über
|
|
||||||
Flexbox in app.css (.app-container mit min-height: 100vh). *@
|
|
||||||
|
|
||||||
@inherits LayoutComponentBase
|
|
||||||
|
|
||||||
<div class="app-container">
|
|
||||||
<Toast />
|
|
||||||
|
|
||||||
@* ── Header ── *@
|
|
||||||
<header class="app-header">
|
|
||||||
<div class="d-flex align-items-center gap-2">
|
|
||||||
@* Im Web-Projekt steht hier ein <img> mit dem signFLOW-Logo.
|
|
||||||
Wir nutzen erstmal Text. Das Logo kommt in Phase 6
|
|
||||||
wenn wir die Bilder aus dem Web-Projekt portieren. *@
|
|
||||||
<span class="app-title">signFLOW</span>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
@* ── Main: Seiteninhalt mit Error-Schutz ──
|
|
||||||
ErrorBoundary fängt unbehandelte Exceptions in Komponenten ab.
|
|
||||||
Ohne ErrorBoundary würde die gesamte App abstürzen.
|
|
||||||
Mit ErrorBoundary zeigen wir stattdessen eine Fehlermeldung
|
|
||||||
und einen "Erneut versuchen"-Button. *@
|
|
||||||
<main class="app-main">
|
|
||||||
<ErrorBoundary @ref="_errorBoundary">
|
|
||||||
<ChildContent>
|
|
||||||
@Body
|
|
||||||
</ChildContent>
|
|
||||||
<ErrorContent Context="ex">
|
|
||||||
<div class="error-container text-center py-5">
|
|
||||||
<div class="status-icon locked mb-3">
|
|
||||||
<i class="bi bi-exclamation-triangle"></i>
|
|
||||||
</div>
|
|
||||||
<h2>Ein unerwarteter Fehler ist aufgetreten</h2>
|
|
||||||
<p class="text-muted">Bitte versuchen Sie es erneut.</p>
|
|
||||||
<button class="btn btn-primary" @onclick="Recover">
|
|
||||||
<i class="bi bi-arrow-counterclockwise me-2"></i>
|
|
||||||
Erneut versuchen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</ErrorContent>
|
|
||||||
</ErrorBoundary>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
@* ── Footer ──
|
|
||||||
Im Web-Projekt gibt es hier drei Elemente:
|
|
||||||
1. Copyright + Link zur Firmenwebsite
|
|
||||||
2. Sprachauswahl (Dropdown mit Flaggen) → kommt in Phase 6
|
|
||||||
3. Privacy-Link (Datenschutzerklärung)
|
|
||||||
|
|
||||||
Die Datenschutz-HTML-Dateien existieren im Web-Projekt unter
|
|
||||||
wwwroot/privacy-policy.de-DE.html. Wir verlinken vorerst
|
|
||||||
auf eine statische URL. Die Datei selbst portieren wir in Phase 6. *@
|
|
||||||
<footer class="app-footer">
|
|
||||||
<small>
|
|
||||||
© signFLOW @DateTime.Now.Year
|
|
||||||
<a href="https://digitaldata.works" target="_blank" class="text-muted text-decoration-none">
|
|
||||||
Digital Data GmbH
|
|
||||||
</a>
|
|
||||||
</small>
|
|
||||||
|
|
||||||
@* Platzhalter für Sprachauswahl — kommt in Phase 6 *@
|
|
||||||
|
|
||||||
<a href="/privacy-policy.de-DE.html" target="_blank" class="text-muted text-decoration-none">
|
|
||||||
<small>Datenschutz</small>
|
|
||||||
</a>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private ErrorBoundary? _errorBoundary;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Setzt die ErrorBoundary zurück.
|
|
||||||
/// Blazor rendert dann @Body erneut statt der Fehlermeldung.
|
|
||||||
/// </summary>
|
|
||||||
private void Recover() => _errorBoundary?.Recover();
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user