From 0ca54fe1fe8d0195b49c78e763fd6b7930f926ee Mon Sep 17 00:00:00 2001 From: TekH Date: Tue, 21 Oct 2025 10:11:36 +0200 Subject: [PATCH] feat(DocSignedNotification): replace Annotations with PsPdfKitAnnotation in DocSignedNotification - Introduced new record `PsPdfKitAnnotation` to encapsulate both Instant and Structured annotation data - Updated `DocSignedNotification` to use `PsPdfKitAnnotation` instead of `ExpandoObject Annotations` - Modified extension methods to accept and map `PsPdfKitAnnotation` - Added reference to `EnvelopeGenerator.Application.Annotations.Commands` for `CreateAnnotationCommand` --- .../DocSigned/DocSignedNotification.cs | 30 ++++++++++++------- .../DocSigned/Handlers/AnnotationHandler.cs | 2 +- .../DocSigned/Handlers/DocStatusHandler.cs | 2 +- .../DocSigned/Handlers/HistoryHandler.cs | 1 - .../Controllers/AnnotationController.cs | 4 +-- EnvelopeGenerator.Web/wwwroot/js/app.js | 4 +-- EnvelopeGenerator.Web/wwwroot/js/app.min.js | 2 +- 7 files changed, 26 insertions(+), 19 deletions(-) diff --git a/EnvelopeGenerator.Application/Common/Notifications/DocSigned/DocSignedNotification.cs b/EnvelopeGenerator.Application/Common/Notifications/DocSigned/DocSignedNotification.cs index 8da5d61f..a51ce574 100644 --- a/EnvelopeGenerator.Application/Common/Notifications/DocSigned/DocSignedNotification.cs +++ b/EnvelopeGenerator.Application/Common/Notifications/DocSigned/DocSignedNotification.cs @@ -1,4 +1,5 @@ -using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver; +using EnvelopeGenerator.Application.Annotations.Commands; +using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver; using EnvelopeGenerator.Application.Common.Extensions; using EnvelopeGenerator.Domain.Constants; using MediatR; @@ -6,6 +7,13 @@ using System.Dynamic; namespace EnvelopeGenerator.Application.Common.Notifications.DocSigned; +/// +/// +/// +/// +/// +public record PsPdfKitAnnotation(ExpandoObject Instant, IEnumerable Structured); + /// /// /// @@ -15,7 +23,7 @@ public record DocSignedNotification(EnvelopeReceiverDto Original) : EnvelopeRece /// /// /// - public required ExpandoObject Annotations { get; init; } + public PsPdfKitAnnotation PsPdfKitAnnotation { get; init; } = null!; /// /// @@ -39,17 +47,17 @@ public static class DocSignedNotificationExtensions /// Converts an to a . /// /// The DTO to convert. - /// + /// /// A new instance. - public static DocSignedNotification ToDocSignedNotification(this EnvelopeReceiverDto dto, ExpandoObject annotations) - => new(dto) { Annotations = annotations }; + public static DocSignedNotification ToDocSignedNotification(this EnvelopeReceiverDto dto, PsPdfKitAnnotation psPdfKitAnnotation) + => new(dto) { PsPdfKitAnnotation = psPdfKitAnnotation }; /// - /// Asynchronously converts a to a . + /// /// - /// The task that returns the DTO to convert. - /// - /// A task that represents the asynchronous conversion operation. - public static async Task ToDocSignedNotification(this Task dtoTask, ExpandoObject annotations) - => await dtoTask is EnvelopeReceiverDto dto ? new(dto) { Annotations = annotations } : null; + /// + /// + /// + public static async Task ToDocSignedNotification(this Task dtoTask, PsPdfKitAnnotation psPdfKitAnnotation) + => await dtoTask is EnvelopeReceiverDto dto ? new(dto) { PsPdfKitAnnotation = psPdfKitAnnotation } : null; } \ No newline at end of file diff --git a/EnvelopeGenerator.Application/Common/Notifications/DocSigned/Handlers/AnnotationHandler.cs b/EnvelopeGenerator.Application/Common/Notifications/DocSigned/Handlers/AnnotationHandler.cs index 41781708..baf6d254 100644 --- a/EnvelopeGenerator.Application/Common/Notifications/DocSigned/Handlers/AnnotationHandler.cs +++ b/EnvelopeGenerator.Application/Common/Notifications/DocSigned/Handlers/AnnotationHandler.cs @@ -33,6 +33,6 @@ public class AnnotationHandler : INotificationHandler { Envelope = new() { Id = notification.EnvelopeId }, Receiver = new() { Id = notification.ReceiverId }, - PSPDFKitInstant = notification.Annotations + PSPDFKitInstant = notification.PsPdfKitAnnotation.Instant }, cancel); } diff --git a/EnvelopeGenerator.Application/Common/Notifications/DocSigned/Handlers/DocStatusHandler.cs b/EnvelopeGenerator.Application/Common/Notifications/DocSigned/Handlers/DocStatusHandler.cs index 306741f4..ca264295 100644 --- a/EnvelopeGenerator.Application/Common/Notifications/DocSigned/Handlers/DocStatusHandler.cs +++ b/EnvelopeGenerator.Application/Common/Notifications/DocSigned/Handlers/DocStatusHandler.cs @@ -33,7 +33,7 @@ public class DocStatusHandler : INotificationHandler { Envelope = new() { Id = notification.EnvelopeId }, Receiver = new() { Id = notification.ReceiverId}, - Value = JsonSerializer.Serialize(notification.Annotations, Format.Json.ForAnnotations) + Value = JsonSerializer.Serialize(notification.PsPdfKitAnnotation.Instant, Format.Json.ForAnnotations) }, cancel); } } \ No newline at end of file diff --git a/EnvelopeGenerator.Application/Common/Notifications/DocSigned/Handlers/HistoryHandler.cs b/EnvelopeGenerator.Application/Common/Notifications/DocSigned/Handlers/HistoryHandler.cs index 8b935aa1..80178e6c 100644 --- a/EnvelopeGenerator.Application/Common/Notifications/DocSigned/Handlers/HistoryHandler.cs +++ b/EnvelopeGenerator.Application/Common/Notifications/DocSigned/Handlers/HistoryHandler.cs @@ -2,7 +2,6 @@ using EnvelopeGenerator.Application.Histories.Commands; using EnvelopeGenerator.Domain.Constants; using MediatR; -using Newtonsoft.Json; namespace EnvelopeGenerator.Application.Common.Notifications.DocSigned.Handlers; diff --git a/EnvelopeGenerator.Web/Controllers/AnnotationController.cs b/EnvelopeGenerator.Web/Controllers/AnnotationController.cs index 26ddfe3b..5b31defb 100644 --- a/EnvelopeGenerator.Web/Controllers/AnnotationController.cs +++ b/EnvelopeGenerator.Web/Controllers/AnnotationController.cs @@ -44,7 +44,7 @@ public class AnnotationController : ControllerBase [Authorize(Roles = ReceiverRole.FullyAuth)] [HttpPost] - public async Task CreateOrUpdate([FromBody] ExpandoObject annotations, CancellationToken cancel = default) + public async Task CreateOrUpdate([FromBody] PsPdfKitAnnotation psPdfKitAnnotation, CancellationToken cancel = default) { // get claims var signature = User.GetAuthReceiverSignature(); @@ -62,7 +62,7 @@ public class AnnotationController : ControllerBase var docSignedNotification = await _mediator .ReadEnvelopeReceiverAsync(uuid, signature, cancel) - .ToDocSignedNotification(annotations) + .ToDocSignedNotification(psPdfKitAnnotation) ?? throw new NotFoundException("Envelope receiver is not found."); await _mediator.Publish(docSignedNotification, cancel); diff --git a/EnvelopeGenerator.Web/wwwroot/js/app.js b/EnvelopeGenerator.Web/wwwroot/js/app.js index 93d138ad..c6f942ee 100644 --- a/EnvelopeGenerator.Web/wwwroot/js/app.js +++ b/EnvelopeGenerator.Web/wwwroot/js/app.js @@ -250,8 +250,8 @@ class App { // Export annotation data and save to database try { const res = await signEnvelope({ - psPfKitInstant: iJSON, - psPfKitStructured: mapSignature(iJSON) + instant: iJSON, + structured: mapSignature(iJSON) }); if (!res.ok) { diff --git a/EnvelopeGenerator.Web/wwwroot/js/app.min.js b/EnvelopeGenerator.Web/wwwroot/js/app.min.js index ee7861af..1ceee6c2 100644 --- a/EnvelopeGenerator.Web/wwwroot/js/app.min.js +++ b/EnvelopeGenerator.Web/wwwroot/js/app.min.js @@ -1,3 +1,3 @@ class App{constructor(n,t,i,r,u,f){this.container=f??`#${this.constructor.name.toLowerCase()}`;this.envelopeKey=n;this.pdfKit=null;this.currentDocument=t.envelope.documents[0];this.currentReceiver=t.receiver;this.signatureCount=t.envelope.documents[0].elements.length;this.envelopeReceiver=t;this.documentBytes=i;this.licenseKey=r;this.locale=u}async init(){this.pdfKit=await loadPSPDFKit(this.documentBytes,this.container,this.licenseKey,this.locale);addToolbarItems(this.pdfKit,this.handleClick.bind(this));this.pdfKit.addEventListener("annotations.load",this.handleAnnotationsLoad.bind(this));this.pdfKit.addEventListener("annotations.change",this.handleAnnotationsChange.bind(this));this.pdfKit.addEventListener("annotations.create",this.handleAnnotationsCreate.bind(this));this.pdfKit.addEventListener("annotations.willChange",()=>{Comp.ActPanel.Toggle()});try{let n=await createAnnotations(this.currentDocument,this.envelopeReceiver.envelopeId,this.envelopeReceiver.receiverId);await this.pdfKit.create(n)}catch(n){console.error("Error loading annotations:",n)}[...document.getElementsByClassName("btn_refresh")].forEach(n=>n.addEventListener("click",()=>this.handleClick("RESET")));[...document.getElementsByClassName("btn_complete")].forEach(n=>n.addEventListener("click",()=>this.handleClick("FINISH")));[...document.getElementsByClassName("btn_reject")].forEach(n=>n.addEventListener("click",()=>this.handleClick("REJECT")))}handleAnnotationsLoad(n){n.toJS()}handleAnnotationsChange(){}async handleAnnotationsCreate(n){const t=n.toJS()[0],i=!!t.formFieldName,r=!!t.isSignature;if(i===!1&&r===!0){const r=t.boundingBox.left-20,u=t.boundingBox.top-20,n=150,i=75,f=new Date,e=await createAnnotationFrameBlob(this.envelopeReceiver.name,this.currentReceiver.signature,f,n,i),o=await fetch(e),s=await o.blob(),h=await this.pdfKit.createAttachment(s),c=createImageAnnotation(new PSPDFKit.Geometry.Rect({left:r,top:u,width:n,height:i}),t.pageIndex,h,generateId(this.envelopeReceiver.envelopeId,this.envelopeReceiver.receiverId,this.fakeElementId--,"signed"));this.pdfKit.create(c)}}async handleClick(n){let t=!1;switch(n){case"RESET":t=await this.handleReset(null);Comp.SignatureProgress.SignedCount=0;t.isConfirmed&&Swal.fire({title:"Erfolg",text:"Dokument wurde zurückgesetzt",icon:"info"});break;case"FINISH":t=await this.handleFinish(null);t==!0&&(window.location.href=`/Envelope/${this.envelopeKey}`);break;case"REJECT":Swal.fire({title:localized.rejection,html:`
${localized.rejectionReasonQ}
`,icon:"question",input:"text",inputAttributes:{autocapitalize:"off"},showCancelButton:!0,confirmButtonColor:"#3085d6",cancelButtonColor:"#d33",confirmButtonText:localized.complete,cancelButtonText:localized.back,showLoaderOnConfirm:!0,preConfirm:async n=>{try{return await rejectEnvelope(n)}catch(t){Swal.showValidationMessage(` Request failed: ${t} - `)}},allowOutsideClick:()=>!Swal.isLoading()}).then(n=>{if(n.isConfirmed){const t=n.value;t.ok?reload():Swal.showValidationMessage(`Request failed: ${t.message}`)}});break;case"COPY_URL":const n=window.location.href.replace(/\/readonly/gi,"");navigator.clipboard.writeText(n).then(function(){bsNotify("Kopiert",{alert_type:"success",delay:4,icon_name:"check_circle"})}).catch(function(){bsNotify("Unerwarteter Fehler",{alert_type:"danger",delay:4,icon_name:"error"})});break;case"SHARE":Comp.ShareBackdrop.show();break;case"LOGOUT":await logout()}}async handleFinish(){const n=await this.pdfKit.exportInstantJSON(),t=n.formFieldValues,r=t.filter(n=>isFieldRequired(n)),u=r.some(n=>n.value===undefined||n.value===null||n.value==="");if(u)return Swal.fire({title:"Warnung",text:"Bitte füllen Sie alle Standortinformationen vollständig aus!",icon:"warning"}),!1;const f=new RegExp("^[a-zA-Z\\u0080-\\u024F]+(?:([\\ \\-\\']|(\\.\\ ))[a-zA-Z\\u0080-\\u024F]+)*$"),e=t.filter(n=>isCityField(n));for(var i of e)if(!IS_MOBILE_DEVICE&&!f.test(i.value))return Swal.fire({title:"Warnung",text:`Bitte überprüfen Sie die eingegebene Ortsangabe "${i.value}" auf korrekte Formatierung. Beispiele für richtige Formate sind: München, Île-de-France, Sauðárkrókur, San Francisco, St. Catharines usw.`,icon:"warning"}),!1;const o=await this.validateAnnotations(this.signatureCount);return o===!1?(Swal.fire({title:"Warnung",text:"Es wurden nicht alle Signaturfelder ausgefüllt!",icon:"warning"}),!1):Swal.fire({title:localized.confirmation,html:`
${localized.sigAgree}
`,icon:"question",showCancelButton:!0,confirmButtonColor:"#3085d6",cancelButtonColor:"#d33",confirmButtonText:localized.finalize,cancelButtonText:localized.back}).then(async t=>{if(t.isConfirmed){try{await this.pdfKit.save()}catch(i){return Swal.fire({title:"Fehler",text:"Umschlag konnte nicht signiert werden!",icon:"error"}),!1}try{const t=await signEnvelope({psPfKitInstant:n,psPfKitStructured:mapSignature(n)});if(t.ok)return!0;if(t.status===403)return Swal.fire({title:"Warnung",text:"Umschlag ist nicht mehr verfügbar.",icon:"warning"}),!1;throw new Error;}catch(i){return Swal.fire({title:"Fehler",text:"Umschlag konnte nicht signiert werden!",icon:"error"}),!1}}else return!1})}async validateAnnotations(n){const t=await getAnnotations(this.pdfKit),i=t.map(n=>n.toJS()).filter(n=>n.isSignature);return n<=i.length}async handleReset(){const n=Swal.fire({title:"Sind sie sicher?",text:"Wollen Sie das Dokument und alle erstellten Signaturen zurücksetzen?",icon:"question",showCancelButton:!0});if(n.isConfirmed){const n=await deleteAnnotations(this.pdfKit)}return n}fakeElementId=0;} \ No newline at end of file + `)}},allowOutsideClick:()=>!Swal.isLoading()}).then(n=>{if(n.isConfirmed){const t=n.value;t.ok?reload():Swal.showValidationMessage(`Request failed: ${t.message}`)}});break;case"COPY_URL":const n=window.location.href.replace(/\/readonly/gi,"");navigator.clipboard.writeText(n).then(function(){bsNotify("Kopiert",{alert_type:"success",delay:4,icon_name:"check_circle"})}).catch(function(){bsNotify("Unerwarteter Fehler",{alert_type:"danger",delay:4,icon_name:"error"})});break;case"SHARE":Comp.ShareBackdrop.show();break;case"LOGOUT":await logout()}}async handleFinish(){const n=await this.pdfKit.exportInstantJSON(),t=n.formFieldValues,r=t.filter(n=>isFieldRequired(n)),u=r.some(n=>n.value===undefined||n.value===null||n.value==="");if(u)return Swal.fire({title:"Warnung",text:"Bitte füllen Sie alle Standortinformationen vollständig aus!",icon:"warning"}),!1;const f=new RegExp("^[a-zA-Z\\u0080-\\u024F]+(?:([\\ \\-\\']|(\\.\\ ))[a-zA-Z\\u0080-\\u024F]+)*$"),e=t.filter(n=>isCityField(n));for(var i of e)if(!IS_MOBILE_DEVICE&&!f.test(i.value))return Swal.fire({title:"Warnung",text:`Bitte überprüfen Sie die eingegebene Ortsangabe "${i.value}" auf korrekte Formatierung. Beispiele für richtige Formate sind: München, Île-de-France, Sauðárkrókur, San Francisco, St. Catharines usw.`,icon:"warning"}),!1;const o=await this.validateAnnotations(this.signatureCount);return o===!1?(Swal.fire({title:"Warnung",text:"Es wurden nicht alle Signaturfelder ausgefüllt!",icon:"warning"}),!1):Swal.fire({title:localized.confirmation,html:`
${localized.sigAgree}
`,icon:"question",showCancelButton:!0,confirmButtonColor:"#3085d6",cancelButtonColor:"#d33",confirmButtonText:localized.finalize,cancelButtonText:localized.back}).then(async t=>{if(t.isConfirmed){try{await this.pdfKit.save()}catch(i){return Swal.fire({title:"Fehler",text:"Umschlag konnte nicht signiert werden!",icon:"error"}),!1}try{const t=await signEnvelope({instant:n,structured:mapSignature(n)});if(t.ok)return!0;if(t.status===403)return Swal.fire({title:"Warnung",text:"Umschlag ist nicht mehr verfügbar.",icon:"warning"}),!1;throw new Error;}catch(i){return Swal.fire({title:"Fehler",text:"Umschlag konnte nicht signiert werden!",icon:"error"}),!1}}else return!1})}async validateAnnotations(n){const t=await getAnnotations(this.pdfKit),i=t.map(n=>n.toJS()).filter(n=>n.isSignature);return n<=i.length}async handleReset(){const n=Swal.fire({title:"Sind sie sicher?",text:"Wollen Sie das Dokument und alle erstellten Signaturen zurücksetzen?",icon:"question",showCancelButton:!0});if(n.isConfirmed){const n=await deleteAnnotations(this.pdfKit)}return n}fakeElementId=0;} \ No newline at end of file