From 111bef14e1ae6a12ce81475f425477c1c91a93ec Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 24 Mar 2026 09:06:48 +0200 Subject: [PATCH 01/16] update rails --- Gemfile.lock | 112 +++++++++++++++++++++++++-------------------------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index ce504fee..d97a7bc9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,31 +1,31 @@ GEM remote: https://rubygems.org/ specs: - action_text-trix (2.1.16) + action_text-trix (2.1.17) railties - actioncable (8.1.2) - actionpack (= 8.1.2) - activesupport (= 8.1.2) + actioncable (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.1.2) - actionpack (= 8.1.2) - activejob (= 8.1.2) - activerecord (= 8.1.2) - activestorage (= 8.1.2) - activesupport (= 8.1.2) + actionmailbox (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) mail (>= 2.8.0) - actionmailer (8.1.2) - actionpack (= 8.1.2) - actionview (= 8.1.2) - activejob (= 8.1.2) - activesupport (= 8.1.2) + actionmailer (8.1.3) + actionpack (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activesupport (= 8.1.3) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.1.2) - actionview (= 8.1.2) - activesupport (= 8.1.2) + actionpack (8.1.3) + actionview (= 8.1.3) + activesupport (= 8.1.3) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -33,36 +33,36 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.1.2) + actiontext (8.1.3) action_text-trix (~> 2.1.15) - actionpack (= 8.1.2) - activerecord (= 8.1.2) - activestorage (= 8.1.2) - activesupport (= 8.1.2) + actionpack (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.1.2) - activesupport (= 8.1.2) + actionview (8.1.3) + activesupport (= 8.1.3) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activejob (8.1.2) - activesupport (= 8.1.2) + activejob (8.1.3) + activesupport (= 8.1.3) globalid (>= 0.3.6) - activemodel (8.1.2) - activesupport (= 8.1.2) - activerecord (8.1.2) - activemodel (= 8.1.2) - activesupport (= 8.1.2) + activemodel (8.1.3) + activesupport (= 8.1.3) + activerecord (8.1.3) + activemodel (= 8.1.3) + activesupport (= 8.1.3) timeout (>= 0.4.0) - activestorage (8.1.2) - actionpack (= 8.1.2) - activejob (= 8.1.2) - activerecord (= 8.1.2) - activesupport (= 8.1.2) + activestorage (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activesupport (= 8.1.3) marcel (~> 1.0) - activesupport (8.1.2) + activesupport (8.1.3) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) @@ -318,7 +318,7 @@ GEM multi_json (1.19.1) net-http (0.9.1) uri (>= 0.11.1) - net-imap (0.6.2) + net-imap (0.6.3) date net-protocol net-pop (0.1.2) @@ -394,20 +394,20 @@ GEM rack (>= 1.3) rackup (2.3.1) rack (>= 3) - rails (8.1.2) - actioncable (= 8.1.2) - actionmailbox (= 8.1.2) - actionmailer (= 8.1.2) - actionpack (= 8.1.2) - actiontext (= 8.1.2) - actionview (= 8.1.2) - activejob (= 8.1.2) - activemodel (= 8.1.2) - activerecord (= 8.1.2) - activestorage (= 8.1.2) - activesupport (= 8.1.2) + rails (8.1.3) + actioncable (= 8.1.3) + actionmailbox (= 8.1.3) + actionmailer (= 8.1.3) + actionpack (= 8.1.3) + actiontext (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activemodel (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) bundler (>= 1.15.0) - railties (= 8.1.2) + railties (= 8.1.3) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -418,9 +418,9 @@ GEM rails-i18n (8.1.0) i18n (>= 0.7, < 2) railties (>= 8.0.0, < 9) - railties (8.1.2) - actionpack (= 8.1.2) - activesupport (= 8.1.2) + railties (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) @@ -543,7 +543,7 @@ GEM activemodel (>= 3.0, < 9.0) strscan (3.1.7) thor (1.5.0) - timeout (0.6.0) + timeout (0.6.1) trailblazer-option (0.1.2) trilogy (2.10.0) bigdecimal From ba2da69a6790f6d1d38a7529e451fce192715f22 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 26 Mar 2026 08:29:17 +0200 Subject: [PATCH 02/16] fix load ico --- lib/load_ico.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/load_ico.rb b/lib/load_ico.rb index 2c48d766..a8c3061d 100644 --- a/lib/load_ico.rb +++ b/lib/load_ico.rb @@ -2,6 +2,7 @@ module LoadIco BI_RGB = 0 + PNG_SIGNATURE = "\x89PNG".b module_function @@ -42,6 +43,8 @@ module LoadIco raise ArgumentError, 'Unable to load' unless image_data_bytes && image_data_bytes.bytesize == best_entry[:size] + return Vips::Image.new_from_buffer(image_data_bytes, '') if image_data_bytes.start_with?(PNG_SIGNATURE) + image = load_image_entry(image_data_bytes, best_entry[:width], best_entry[:height]) raise ArgumentError, 'Unable to load' unless image From 67de153016a9108e3ed713f54c0cd56c35800ecf Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 26 Mar 2026 11:38:21 +0200 Subject: [PATCH 03/16] increase image limit --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 23ee1f4b..6a7db422 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,7 +49,6 @@ ENV RAILS_ENV=production ENV BUNDLE_WITHOUT="development:test" ENV LD_PRELOAD=/lib/libgcompat.so.0 ENV OPENSSL_CONF=/etc/openssl_legacy.cnf -ENV VIPS_MAX_COORD=15000 WORKDIR /app @@ -98,6 +97,7 @@ RUN ln -s /fonts /app/public/fonts && \ WORKDIR /data/docuseal ENV HOME=/home/docuseal ENV WORKDIR=/data/docuseal +ENV VIPS_MAX_COORD=17000 EXPOSE 3000 CMD ["/app/bin/bundle", "exec", "puma", "-C", "/app/config/puma.rb", "--dir", "/app"] From c23eca0adeb51842369033614d50ae52c243f636 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Thu, 26 Mar 2026 16:09:57 +0200 Subject: [PATCH 04/16] fix field style --- app/views/submissions/_detailed_form.html.erb | 2 +- app/views/submissions/_email_form.html.erb | 2 +- app/views/submissions/_phone_form.html.erb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/submissions/_detailed_form.html.erb b/app/views/submissions/_detailed_form.html.erb index 62f756fd..e6759580 100644 --- a/app/views/submissions/_detailed_form.html.erb +++ b/app/views/submissions/_detailed_form.html.erb @@ -3,7 +3,7 @@ <% submitters = template.submitters.reject { |e| e['invite_by_uuid'].present? || e['optional_invite_by_uuid'].present? || e['invite_via_field_uuid'].present? } %>
-
+
+ <% end %> + <% end %> + <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::ALLOW_TO_DELEGATE_KEY) %> + <% if can?(:manage, account_config) %> + <%= form_for account_config, url: account_configs_path, method: :post do |f| %> + <%= f.hidden_field :key %> +
+
+ <%= t('allow_to_delegate_documents') %> + + <%= svg_icon('info_circle', class: 'hidden md:inline-block w-4 h-4 shrink-0') %> +
- <% end %> + <% if !Docuseal.multitenant? || can?(:manage, :delegate_form) %> + + <%= f.check_box :value, class: 'toggle', checked: account_config.value == true %> + + <% else %> + " data-turbo="false" data-tip="<%= I18n.t('unlock_with_docuseal_pro') %>" class="flex tooltip"> + <%= f.check_box :value, class: 'toggle pointer-events-none', checked: account_config.value == true, disabled: true %> + + <% end %> +
<% end %> <% end %> <% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::FORM_PREFILL_SIGNATURE_KEY) %> diff --git a/app/views/icons/_user_share.html.erb b/app/views/icons/_user_share.html.erb new file mode 100644 index 00000000..ecb37945 --- /dev/null +++ b/app/views/icons/_user_share.html.erb @@ -0,0 +1 @@ + diff --git a/app/views/submission_events/index.html.erb b/app/views/submission_events/index.html.erb index 8dec75f3..5c454288 100644 --- a/app/views/submission_events/index.html.erb +++ b/app/views/submission_events/index.html.erb @@ -20,10 +20,13 @@ <% end %>

+ <% submitter_versions_index = @submission.submitters.each_with_object({}) { |s, h| h[s.id] = s.submitter_versions.to_a.sort_by(&:created_at) } %> <% @submission.submission_events.order(:event_timestamp).each do |event| %> <% submitter = @submission.submitters.find { |e| e.id == event.submitter_id } %> <% bg_class = event_colors[submitters_uuids.index(submitter.uuid) % event_colors.length] %> - <% submitter_name = event.event_type.include?('sms') || event.event_type.include?('phone') ? (event.data['phone'] || submitter.phone) : (submitter.name || submitter.email || submitter.phone) %> + <% versions = submitter_versions_index[submitter.id] || [] %> + <% active_version = versions.find { |v| v.created_at > event.event_timestamp } %> + <% submitter_name = event.event_type.include?('sms') || event.event_type.include?('phone') ? (event.data['phone'] || active_version&.phone || submitter.phone) : (active_version&.name || active_version&.email || active_version&.phone || submitter.name || submitter.email || submitter.phone) %>
  • <%= svg_icon(SubmissionEventsController::SUBMISSION_EVENT_ICONS.fetch(event.event_type, 'circle_dot'), class: 'w-4 h-4') %> @@ -43,6 +46,9 @@ <%= t('submission_event_names.complete_verification_by_html', provider: event.data['method'], submitter_name:) %> <% elsif event.event_type == 'invite_party' && (invited_submitter = @submission.submitters.find { |e| e.uuid == event.data['uuid'] }) && (name = @submission.template_submitters.find { |e| e['uuid'] == event.data['uuid'] }&.dig('name')) %> <%= t('submission_event_names.invite_party_by_html', invited_submitter_name: [invited_submitter.name || invited_submitter.email || invited_submitter.phone, name].join(' '), submitter_name:) %> + <% elsif event.event_type == 'delegate_form' %> + <% delegate_from = event.data['old_email'].presence || (versions.reverse.find { |v| v.created_at <= event.event_timestamp }&.then { |v| v.name || v.phone }) %> + <%= t('submission_event_names.delegate_form_by_html', from: delegate_from, to: event.data['email']) %> <% elsif event.event_type.include?('send_') %> <%= t("submission_event_names.#{event.event_type}_to_html", submitter_name:) %> <% elsif event.event_type.start_with?('bounce_') || event.event_type.start_with?('complaint_') %> diff --git a/app/views/submit_form/_delegate_form.html.erb b/app/views/submit_form/_delegate_form.html.erb new file mode 100644 index 00000000..7e708984 --- /dev/null +++ b/app/views/submit_form/_delegate_form.html.erb @@ -0,0 +1,9 @@ +<%= form_for '', url: submit_form_delegate_index_path(submitter.slug), method: :post do |f| %> +
    + <%= f.label :email, t(:enter_the_email_address_of_the_person_you_want_to_delegate_to), class: 'label' %> + <%= f.email_field :email, required: true, class: 'base-input w-full', dir: 'auto', placeholder: t('email') %> +
    + + <%= f.button button_title(title: t(:delegate)), class: 'base-button' %> + +<% end %> diff --git a/app/views/submit_form/delegated.html.erb b/app/views/submit_form/delegated.html.erb new file mode 100644 index 00000000..de96b574 --- /dev/null +++ b/app/views/submit_form/delegated.html.erb @@ -0,0 +1,21 @@ +
    +
    +
    +
    + <%= render 'start_form/banner' %> +
    +
    +
    +
    + <%= svg_icon('user_share', class: 'w-10 h-10') %> +
    +
    +

    <%= @submitter.submission.name || @submitter.submission.template.name %>

    +

    <%= t('document_has_been_delegated_on_html', time: l(@submitter.submission_events.order(event_timestamp: :desc).find_by!(event_type: :delegate_form).event_timestamp, format: :long)) %>

    +
    +
    +
    +
    +
    +
    +<%= render 'shared/attribution', link_path: '/start', account: @submitter.account %> diff --git a/app/views/submit_form/show.html.erb b/app/views/submit_form/show.html.erb index 54fc3e78..85db38ef 100644 --- a/app/views/submit_form/show.html.erb +++ b/app/views/submit_form/show.html.erb @@ -9,6 +9,7 @@ <% schema = Submissions.filtered_conditions_schema(@submitter.submission, values:, include_submitter_uuid: @submitter.uuid) %> <% font_scale = 1000.0 / PdfUtils::US_LETTER_W %> <% decline_modal_checkbox_uuid = nil %> +<% delegate_modal_checkbox_uuid = nil %>
    @@ -20,42 +21,85 @@ <%= @submitter.submission.name || @submitter.submission.template.name %>
    - <% if @form_configs[:with_decline] %> - - <% end %> - <% if @form_configs[:with_partial_download] %> - - - <%= svg_icon('download', class: 'w-6 h-6 inline md:hidden') %> - - - - + <% if @form_configs[:with_delegate] %> + + <% if @form_configs[:with_decline] %> + + <% end %> + <% if @form_configs[:with_partial_download] %> + + + <%= svg_icon('download', class: 'w-5 h-5') %> + + + + <% end %> + <% else %> + <% if @form_configs[:with_decline] %> + + <% end %> + <% if @form_configs[:with_partial_download] %> + + + <%= svg_icon('download', class: 'w-6 h-6 inline md:hidden') %> + + + + + <% end %> <% end %>
    <% end %> <% schema.each do |item| %> @@ -110,4 +154,9 @@ <%= render 'submit_form/decline_form', submitter: @submitter %> <% end %> <% end %> +<% if @form_configs[:with_delegate] %> + <%= render 'shared/html_modal', title: t(:delegate), uuid: delegate_modal_checkbox_uuid do %> + <%= render 'submit_form/delegate_form', submitter: @submitter %> + <% end %> +<% end %> <%= render 'scripts/autosize_field' %> diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 0b91d8fb..22ec12d5 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -163,10 +163,13 @@ en: &en download: Download decline: Decline declined: Declined + delegate: Delegate + enter_the_email_address_of_the_person_you_want_to_delegate_to: Enter the email address of the person you want to delegate to decline_reason: Decline reason provide_a_reason: Provide a reason notify_the_sender_with_the_reason_you_declined: Notify the sender with the reason you declined form_has_been_declined_on_html: 'Form has been declined on %{time}' + document_has_been_delegated_on_html: 'Document has been delegated on %{time}' name_declined_by_submitter: '"%{name}" declined by %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" has been declined by %{submitter} with the following reason:' completed_successfully: Completed Successfully @@ -201,6 +204,7 @@ en: &en allow_typed_text_signatures: Allow typed text signatures allow_to_resubmit_completed_forms: Allow to resubmit completed forms allow_to_decline_documents: Allow to decline documents + allow_to_delegate_documents: Allow to delegate documents remember_and_pre_fill_signatures: Remember and pre-fill signatures require_authentication_for_file_download_links: Require authentication for file download links combine_completed_documents_and_audit_log: Combine completed documents and Audit Log @@ -841,6 +845,7 @@ en: &en allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Allow signers to create signatures by typing their name instead of drawing or uploading one. allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Allow signers to resubmit forms after completion, useful when corrections or multiple submissions are needed. allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Allow recipients to decline signing a document. The decline reason notification will be sent to the signature requester. + allow_recipients_to_delegate_documents: Allow recipients to delegate documents to a different person via email. This can be useful when the original recipient needs to pass the document to the authorized signer. save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: "Save a user's signature and automatically pre-fill it in future signing sessions." make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Make document download links expire after 40 minutes to prevent long-term access and enhance security. require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Require authentication with user login or API key to access the document download links. @@ -954,6 +959,8 @@ en: &en complete_kba_by_html: 'KBA completed by %{submitter_name}' fail_kba_by_html: 'KBA failed by %{submitter_name}' api_complete_form_by_html: 'Submission completed via API by %{submitter_name}' + decline_form_by_html: 'Form declined by %{submitter_name}' + delegate_form_by_html: 'Delegated from %{from} to %{to}' import_list: select_worksheet: Select Worksheet open: Open @@ -1192,11 +1199,14 @@ es: &es downloading: Descargando download: Descargar decline: Rechazar + delegate: Delegar + enter_the_email_address_of_the_person_you_want_to_delegate_to: Ingrese la dirección de correo electrónico de la persona a quien desea delegar declined: Rechazado decline_reason: Motivo de rechazo provide_a_reason: Proporcione un motivo notify_the_sender_with_the_reason_you_declined: Notifique al remitente con el motivo por el que rechazó form_has_been_declined_on_html: 'El formulario ha sido rechazado el %{time}' + document_has_been_delegated_on_html: 'El documento ha sido delegado el %{time}' name_declined_by_submitter: '"%{name}" rechazado por %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" ha sido rechazado por %{submitter} con el siguiente motivo:' completed_successfully: Completado exitosamente @@ -1231,6 +1241,7 @@ es: &es allow_typed_text_signatures: Permitir firmas de texto mecanografiadas allow_to_resubmit_completed_forms: Permitir reenviar formularios completados allow_to_decline_documents: Permitir rechazar documentos + allow_to_delegate_documents: Permitir delegar documentos remember_and_pre_fill_signatures: Recordar y prellenar firmas require_authentication_for_file_download_links: Requerir autenticación para enlaces de descarga de archivos combine_completed_documents_and_audit_log: Combinar documentos completados y Registro de Auditoría @@ -1868,6 +1879,7 @@ es: &es allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Permitir que los firmantes creen firmas escribiendo su nombre en lugar de dibujar o subir una. allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Permitir que los firmantes vuelvan a enviar formularios después de completarlos, útil cuando se necesitan correcciones o múltiples envíos. allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Permitir que los destinatarios rechacen firmar un documento. La notificación del motivo del rechazo se enviará al solicitante de la firma. + allow_recipients_to_delegate_documents: Permitir que los destinatarios deleguen documentos a otra persona por correo electrónico. Esto puede ser útil cuando el destinatario original necesita pasar el documento al firmante autorizado. save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Guardar la firma de un usuario y rellenarla automáticamente en futuras sesiones de firma. make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Hacer que los enlaces de descarga de documentos caduquen después de 40 minutos para evitar el acceso a largo plazo y mejorar la seguridad. require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Requerir autenticación con inicio de sesión de usuario o clave API para acceder a los enlaces de descarga de documentos. @@ -1976,6 +1988,7 @@ es: &es invite_party_by_html: 'Invitado %{invited_submitter_name} por %{submitter_name}' complete_form_by_html: 'Envío completado por %{submitter_name}' api_complete_form_by_html: 'Envío completado vía API por %{submitter_name}' + delegate_form_by_html: 'Delegado de %{from} a %{to}' start_verification_by_html: 'Verificación de identidad iniciada por %{submitter_name}' complete_verification_by_html: 'Verificación de identidad completada por %{submitter_name} con %{provider}' start_kba_by_html: 'KBA iniciado por %{submitter_name}' @@ -2219,11 +2232,14 @@ it: &it downloading: Scaricamento download: Scarica decline: Rifiuta + delegate: Delega + enter_the_email_address_of_the_person_you_want_to_delegate_to: Inserisci l'indirizzo email della persona a cui vuoi delegare declined: Rifiutato decline_reason: Motivo del rifiuto provide_a_reason: Fornisci un motivo notify_the_sender_with_the_reason_you_declined: Informare il mittente del motivo per cui hai rifiutato form_has_been_declined_on_html: 'Il modulo è stato rifiutato il %{time}' + document_has_been_delegated_on_html: 'Il documento è stato delegato il %{time}' name_declined_by_submitter: '"%{name}" rifiutato da %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" è stato rifiutato da %{submitter} per il seguente motivo:' completed_successfully: Completato con successo @@ -2258,6 +2274,7 @@ it: &it allow_typed_text_signatures: Permettere firme con testo digitato allow_to_resubmit_completed_forms: Permettere di reinviare i moduli completati allow_to_decline_documents: Permettere di rifiutare i documenti + allow_to_delegate_documents: Permettere di delegare i documenti remember_and_pre_fill_signatures: Ricordare e precompilare le firme require_authentication_for_file_download_links: "Richiedere l'autenticazione per i link di download dei file" combine_completed_documents_and_audit_log: Combinare i documenti completati e il Registro di Audit @@ -2896,6 +2913,7 @@ it: &it allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Consentire ai firmatari di creare firme digitando il proprio nome invece di disegnarle o caricarle. allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Consentire ai firmatari di reinviare i moduli dopo il completamento, utile quando sono necessarie correzioni o più invii. allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Consentire ai destinatari di rifiutare di firmare un documento. La notifica del motivo del rifiuto verrà inviata al richiedente della firma. + allow_recipients_to_delegate_documents: Consentire ai destinatari di delegare i documenti a un'altra persona tramite e-mail. Ciò può essere utile quando il destinatario originale deve passare il documento al firmatario autorizzato. save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Salvare la firma di un utente e compilarla automaticamente nelle future sessioni di firma. make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: "Fare in modo che i link di download dei documenti scadano dopo 40 minuti per prevenire l'accesso a lungo termine e migliorare la sicurezza." require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: "Richiedere l'autenticazione con login utente o chiave API per accedere ai link di download dei documenti." @@ -3004,6 +3022,7 @@ it: &it invite_party_by_html: 'Invitato %{invited_submitter_name} da %{submitter_name}' complete_form_by_html: 'Invio completato da %{submitter_name}' api_complete_form_by_html: 'Invio completato tramite API da %{submitter_name}' + delegate_form_by_html: 'Delegato da %{from} a %{to}' start_verification_by_html: "Verifica dell'identità iniziata da %{submitter_name}" complete_verification_by_html: "Verifica dell'identità completata da %{submitter_name} con %{provider}" start_kba_by_html: "KBA avviata da %{submitter_name}" @@ -3247,11 +3266,14 @@ fr: &fr downloading: Téléchargement download: Télécharger decline: Refuser + delegate: Déléguer + enter_the_email_address_of_the_person_you_want_to_delegate_to: Saisissez l'adresse e-mail de la personne à qui vous souhaitez déléguer declined: Refusé decline_reason: Motif du refus provide_a_reason: Fournissez un motif notify_the_sender_with_the_reason_you_declined: Notifier l’expéditeur avec le motif du refus form_has_been_declined_on_html: Le formulaire a été refusé le %{time} + document_has_been_delegated_on_html: Le document a été délégué le %{time} name_declined_by_submitter: '"%{name}" refusé par %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" a été refusé par %{submitter} pour le motif suivant :' completed_successfully: Terminé avec succès @@ -3286,6 +3308,7 @@ fr: &fr allow_typed_text_signatures: Autoriser les signatures tapées allow_to_resubmit_completed_forms: Autoriser la nouvelle soumission des formulaires complétés allow_to_decline_documents: Autoriser le refus de documents + allow_to_delegate_documents: Autoriser la délégation de documents remember_and_pre_fill_signatures: Mémoriser et pré-remplir les signatures require_authentication_for_file_download_links: Exiger une authentification pour les liens de téléchargement combine_completed_documents_and_audit_log: Combiner les documents complétés et le journal d’audit @@ -3919,6 +3942,7 @@ fr: &fr allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Autoriser les signataires à créer des signatures en tapant leur nom au lieu de dessiner ou téléverser une signature. allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Autoriser les signataires à soumettre à nouveau les formulaires après finalisation, utile pour des corrections ou soumissions multiples. allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Autoriser les destinataires à refuser de signer un document. Le motif du refus sera notifié au demandeur de signature. + allow_recipients_to_delegate_documents: Autoriser les destinataires à déléguer des documents à une autre personne par e-mail. Cela peut être utile lorsque le destinataire d'origine doit transmettre le document au signataire autorisé. save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Mémoriser la signature d’un utilisateur et la pré‑remplir automatiquement lors de futures sessions de signature. make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Faire expirer les liens de téléchargement des documents après 40 minutes pour éviter un accès à long terme et renforcer la sécurité. require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Exiger une authentification par connexion utilisateur ou clé API pour accéder aux liens de téléchargement des documents. @@ -4030,6 +4054,7 @@ fr: &fr start_verification_by_html: "Vérification d’identité démarrée par %{submitter_name}" complete_verification_by_html: "Vérification d’identité terminée par %{submitter_name} avec %{provider}" api_complete_form_by_html: "Soumission terminée via API par %{submitter_name}" + delegate_form_by_html: "Délégué de %{from} à %{to}" start_kba_by_html: "KBA démarré par %{submitter_name}" complete_kba_by_html: "KBA terminé par %{submitter_name}" fail_kba_by_html: "KBA échoué par %{submitter_name}" @@ -4271,11 +4296,14 @@ pt: &pt downloading: Baixando download: Baixar decline: Recusar + delegate: Delegar + enter_the_email_address_of_the_person_you_want_to_delegate_to: Insira o endereço de e-mail da pessoa para quem deseja delegar declined: Recusado decline_reason: Motivo da recusa provide_a_reason: Forneça um motivo notify_the_sender_with_the_reason_you_declined: Notifique o remetente com o motivo da sua recusa form_has_been_declined_on_html: "O formulário foi recusado em %{time}" + document_has_been_delegated_on_html: "O documento foi delegado em %{time}" name_declined_by_submitter: '"%{name}" foi recusado por %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" foi recusado por %{submitter} com o seguinte motivo:' completed_successfully: Concluído com sucesso @@ -4310,6 +4338,7 @@ pt: &pt allow_typed_text_signatures: Permitir assinaturas digitadas allow_to_resubmit_completed_forms: Permitir reenviar formulários concluídos allow_to_decline_documents: Permitir recusar documentos + allow_to_delegate_documents: Permitir delegar documentos remember_and_pre_fill_signatures: Lembrar e preencher assinaturas automaticamente require_authentication_for_file_download_links: Requerer autenticação para links de download de arquivos combine_completed_documents_and_audit_log: Combinar documentos concluídos e log de auditoria @@ -4947,6 +4976,7 @@ pt: &pt allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Permitir que os signatários criem assinaturas digitando seu nome em vez de desenhá-las ou carregá-las. allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Permitir que os signatários reenviem formulários após o término, útil quando são necessárias correções ou múltiplos envios. allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Permitir que os destinatários recusem assinar um documento. A notificação do motivo da recusa será enviada ao solicitante da assinatura. + allow_recipients_to_delegate_documents: Permitir que os destinatários deleguem documentos a outra pessoa por e-mail. Isso pode ser útil quando o destinatário original precisa passar o documento ao signatário autorizado. save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Salvar a assinatura de um usuário e preenchê-la automaticamente em futuras sessões de assinatura. make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Fazer os links de download de documentos expirarem após 40 minutos para evitar o acesso prolongado e aumentar a segurança. require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Exigir autenticação com login do usuário ou chave API para acessar os links de download de documentos. @@ -5057,6 +5087,7 @@ pt: &pt start_verification_by_html: 'Verificação de identidade iniciada por %{submitter_name}' complete_verification_by_html: 'Verificação de identidade concluída por %{submitter_name} com %{provider}' api_complete_form_by_html: 'Submissão concluída via API por %{submitter_name}' + delegate_form_by_html: 'Delegado de %{from} para %{to}' start_kba_by_html: 'KBA iniciada por %{submitter_name}' complete_kba_by_html: 'KBA concluída por %{submitter_name}' fail_kba_by_html: 'KBA reprovada por %{submitter_name}' @@ -5298,11 +5329,14 @@ de: &de downloading: Wird heruntergeladen download: Download decline: Ablehnen + delegate: Delegieren + enter_the_email_address_of_the_person_you_want_to_delegate_to: Geben Sie die E-Mail-Adresse der Person ein, an die Sie delegieren möchten declined: Abgelehnt decline_reason: Ablehnungsgrund provide_a_reason: Geben Sie einen Grund an notify_the_sender_with_the_reason_you_declined: Den Absender über den Grund Ihrer Ablehnung benachrichtigen form_has_been_declined_on_html: "Das Formular wurde am %{time} abgelehnt" + document_has_been_delegated_on_html: "Das Dokument wurde am %{time} delegiert" name_declined_by_submitter: '"%{name}" wurde von %{submitter} abgelehnt' name_declined_by_submitter_with_the_following_reason: '"%{name}" wurde von %{submitter} mit folgendem Grund abgelehnt:' completed_successfully: Erfolgreich abgeschlossen @@ -5337,6 +5371,7 @@ de: &de allow_typed_text_signatures: Getippte Unterschriften zulassen allow_to_resubmit_completed_forms: Erneutes Einreichen abgeschlossener Formulare zulassen allow_to_decline_documents: Ablehnen von Dokumenten erlauben + allow_to_delegate_documents: Delegieren von Dokumenten erlauben remember_and_pre_fill_signatures: Signaturen merken und vorab ausfüllen require_authentication_for_file_download_links: Authentifizierung für Dateidownload-Links erforderlich combine_completed_documents_and_audit_log: Abgeschlossene Dokumente und Prüfprotokoll kombinieren @@ -5974,6 +6009,7 @@ de: &de allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Unterzeichner dürfen Signaturen erstellen, indem sie ihren Namen eingeben, statt eine Signatur zu zeichnen oder hochzuladen. allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Unterzeichner dürfen Formulare nach Abschluss erneut einreichen – nützlich für Korrekturen oder mehrere Einreichungen. allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Empfänger dürfen die Unterzeichnung eines Dokuments ablehnen. Die Benachrichtigung mit dem Ablehnungsgrund wird an den Anforderer der Signatur gesendet. + allow_recipients_to_delegate_documents: Empfängern erlauben, Dokumente per E-Mail an eine andere Person weiterzuleiten. Dies kann nützlich sein, wenn der ursprüngliche Empfänger das Dokument an den autorisierten Unterzeichner weiterleiten muss. save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Die Signatur eines Benutzers speichern und sie in zukünftigen Signatursitzungen automatisch vorausfüllen. make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Download-Links für Dokumente nach 40 Minuten ablaufen lassen, um langfristigen Zugriff zu verhindern und die Sicherheit zu erhöhen. require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Authentifizierung mit Benutzeranmeldung oder API-Schlüssel erforderlich, um auf die Download-Links für Dokumente zuzugreifen. @@ -6084,6 +6120,7 @@ de: &de start_verification_by_html: 'Identitätsüberprüfung gestartet von %{submitter_name}' complete_verification_by_html: 'Identitätsüberprüfung abgeschlossen von %{submitter_name} mit %{provider}' api_complete_form_by_html: 'Einreichung über API abgeschlossen von %{submitter_name}' + delegate_form_by_html: 'Delegiert von %{from} an %{to}' start_kba_by_html: 'KBA gestartet von %{submitter_name}' complete_kba_by_html: 'KBA abgeschlossen von %{submitter_name}' fail_kba_by_html: 'KBA fehlgeschlagen von %{submitter_name}' @@ -6190,11 +6227,14 @@ pl: hi_there: Cześć, download: Pobierz decline: Odrzuć + delegate: Deleguj + enter_the_email_address_of_the_person_you_want_to_delegate_to: Wprowadź adres e-mail osoby, do której chcesz delegować declined: Odrzucono decline_reason: Powód odrzucenia provide_a_reason: Podaj powód notify_the_sender_with_the_reason_you_declined: Powiadom nadawcę o powodzie, dla którego odrzuciłeś form_has_been_declined_on_html: 'Formularz został odrzucony o %{time}' + document_has_been_delegated_on_html: 'Dokument został oddelegowany o %{time}' name_declined_by_submitter: '"%{name}" odrzucono przez %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" został odrzucony przez %{submitter} z następującym powodem:' email: E-mail @@ -6287,11 +6327,14 @@ uk: hi_there: Привіт, download: Завантажити decline: Відхилити + delegate: Делегувати + enter_the_email_address_of_the_person_you_want_to_delegate_to: Введіть адресу електронної пошти особи, якій ви хочете делегувати declined: Відхилено decline_reason: Причина відхилення provide_a_reason: Надайте причину notify_the_sender_with_the_reason_you_declined: Повідомте відправника про причину, з якої ви відхилили form_has_been_declined_on_html: 'Форму було відхилено о %{time}' + document_has_been_delegated_on_html: 'Документ було делеговано о %{time}' name_declined_by_submitter: '"%{name}" відхилено %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" було відхилено %{submitter} з такою причиною:' email: E-mail @@ -6384,11 +6427,14 @@ cs: hi_there: Ahoj, download: Stáhnout decline: Odmítnout + delegate: Delegovat + enter_the_email_address_of_the_person_you_want_to_delegate_to: Zadejte e-mailovou adresu osoby, na kterou chcete delegovat declined: Odmítnuto decline_reason: Důvod odmítnutí provide_a_reason: Uveďte důvod notify_the_sender_with_the_reason_you_declined: Informujte odesílatele o důvodu odmítnutí form_has_been_declined_on_html: 'Formulář byl odmítnut dne %{time}' + document_has_been_delegated_on_html: 'Dokument byl delegován dne %{time}' name_declined_by_submitter: '"%{name}" odmítnuto %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" bylo odmítnuto %{submitter} s následujícím důvodem:' email: E-mail @@ -6481,11 +6527,14 @@ he: hi_there: שלום, download: הורד decline: דחייה + delegate: הואלה + enter_the_email_address_of_the_person_you_want_to_delegate_to: הזן את כתובת הדוא"ל של האדם שברצונך להאציל אליו declined: דחוי decline_reason: סיבת דחייה provide_a_reason: אנא ספק סיבה notify_the_sender_with_the_reason_you_declined: הודע לשולח על הסיבה לדחייה form_has_been_declined_on_html: 'הטופס נדחה בתאריך %{time}' + document_has_been_delegated_on_html: 'המסמך הועבר בתאריך %{time}' name_declined_by_submitter: '"%{name}" נדחה על ידי %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" נדחה על ידי %{submitter} עם הסיבה הבאה:' email: דוא"ל @@ -6714,11 +6763,14 @@ nl: &nl downloading: Downloaden download: Downloaden decline: Weigeren + delegate: Delegeren + enter_the_email_address_of_the_person_you_want_to_delegate_to: Voer het e-mailadres in van de persoon aan wie u wilt delegeren declined: Geweigerd decline_reason: Reden van weigering provide_a_reason: Geef een reden op notify_the_sender_with_the_reason_you_declined: Informeer de verzender over de reden van uw weigering form_has_been_declined_on_html: Formulier is geweigerd op %{time} + document_has_been_delegated_on_html: Document is gedelegeerd op %{time} name_declined_by_submitter: '"%{name}" geweigerd door %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" is geweigerd door %{submitter} met de volgende reden:' completed_successfully: Succesvol voltooid @@ -6753,6 +6805,7 @@ nl: &nl allow_typed_text_signatures: Handtekeningen met getypte tekst toestaan allow_to_resubmit_completed_forms: Opnieuw indienen van voltooide formulieren toestaan allow_to_decline_documents: Weigeren van documenten toestaan + allow_to_delegate_documents: Delegeren van documenten toestaan remember_and_pre_fill_signatures: Handtekeningen onthouden en vooraf invullen require_authentication_for_file_download_links: Authenticatie vereisen voor links naar bestandsdownloads combine_completed_documents_and_audit_log: Voltooide documenten en auditlogboek combineren @@ -7386,6 +7439,7 @@ nl: &nl allow_signers_to_create_signatures_by_typing_their_name_instead_of_drawing_or_uploading_one: Sta ondertekenaars toe handtekeningen te maken door hun naam te typen in plaats van te tekenen of te uploaden. allow_signers_to_resubmit_forms_after_completion_useful_when_corrections_or_multiple_submissions_are_needed: Sta ondertekenaars toe formulieren na voltooiing opnieuw in te dienen; nuttig wanneer correcties of meerdere inzendingen nodig zijn. allow_recipients_to_decline_signing_a_document_the_decline_reason_notification_will_be_sent_to_the_signature_requester: Sta ontvangers toe het ondertekenen van een document te weigeren. De melding met de reden van weigering wordt naar de aanvrager van de handtekening gestuurd. + allow_recipients_to_delegate_documents: Sta ontvangers toe documenten per e-mail te delegeren aan een andere persoon. Dit kan handig zijn wanneer de oorspronkelijke ontvanger het document moet doorgeven aan de gemachtigde ondertekenaar. save_a_users_signature_and_automatically_pre_fill_it_in_future_signing_sessions: Sla de handtekening van een gebruiker op en vul deze automatisch vooraf in bij toekomstige ondertekeningssessies. make_document_download_links_expire_after_40_minutes_to_prevent_long_term_access_and_enhance_security: Laat links voor het downloaden van documenten na 40 minuten verlopen om langdurige toegang te voorkomen en de beveiliging te verbeteren. require_authentication_with_user_login_or_api_key_to_access_the_document_download_links: Authenticatie met gebruikerslogin of API-sleutel vereisen om toegang te krijgen tot de documentdownloadlinks. @@ -7496,6 +7550,7 @@ nl: &nl start_verification_by_html: "Identiteitsverificatie gestart door %{submitter_name}" complete_verification_by_html: "Identiteitsverificatie voltooid door %{submitter_name} met %{provider}" api_complete_form_by_html: "Inzending via API voltooid door %{submitter_name}" + delegate_form_by_html: "Gedelegeerd van %{from} naar %{to}" start_kba_by_html: "KBA gestart door %{submitter_name}" complete_kba_by_html: "KBA voltooid door %{submitter_name}" fail_kba_by_html: "KBA mislukt door %{submitter_name}" @@ -7602,11 +7657,14 @@ ar: hi_there: مرحبا, download: تحميل decline: رفض + delegate: تفويض + enter_the_email_address_of_the_person_you_want_to_delegate_to: أدخل عنوان البريد الإلكتروني للشخص الذي تريد التفويض إليه declined: مرفوض decline_reason: سبب الرفض provide_a_reason: قدم سببًا notify_the_sender_with_the_reason_you_declined: أخطر المرسل بسبب الرفض form_has_been_declined_on_html: 'تم رفض النموذج في %{time}' + document_has_been_delegated_on_html: 'تم تفويض المستند في %{time}' name_declined_by_submitter: '"%{name}" مرفوض بواسطة %{submitter}' name_declined_by_submitter_with_the_following_reason: '"%{name}" تم رفضه بواسطة %{submitter} مع السبب التالي:' email: البريد الإلكتروني @@ -7699,11 +7757,14 @@ ko: hi_there: 안녕하세요, download: 다운로드 decline: 거절 + delegate: 위임 + enter_the_email_address_of_the_person_you_want_to_delegate_to: 위임할 사람의 이메일 주소를 입력하세요 declined: 거절됨 decline_reason: 거절 사유 provide_a_reason: 사유를 제공하세요 notify_the_sender_with_the_reason_you_declined: 거절한 이유로 발신자에게 알리기 form_has_been_declined_on_html: '양식이 %{time}에 거절되었습니다.' + document_has_been_delegated_on_html: '문서가 %{time}에 위임되었습니다.' name_declined_by_submitter: '"%{name}"이(가) %{submitter}에 의해 거절되었습니다.' name_declined_by_submitter_with_the_following_reason: '"%{name}"이(가) %{submitter}에 의해 다음과 같은 사유로 거절되었습니다:' email: 이메일 @@ -7796,11 +7857,14 @@ ja: hi_there: こんにちは download: ダウンロード decline: 辞退 + delegate: 委任 + enter_the_email_address_of_the_person_you_want_to_delegate_to: 委任したい人のメールアドレスを入力してください declined: 辞退済み decline_reason: 辞退の理由 provide_a_reason: 理由を入力してください notify_the_sender_with_the_reason_you_declined: 辞退理由を送信者に通知してください form_has_been_declined_on_html: '%{time} にフォームが辞退されました' + document_has_been_delegated_on_html: '%{time} にドキュメントが委任されました' name_declined_by_submitter: '"%{name}" は %{submitter} により辞退されました' name_declined_by_submitter_with_the_following_reason: '"%{name}" は %{submitter} により次の理由で辞退されました:' email: メール diff --git a/config/routes.rb b/config/routes.rb index ff7e1089..ee700493 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -145,8 +145,10 @@ Rails.application.routes.draw do resources :values, only: %i[index], controller: 'submit_form_values' resources :download, only: %i[index], controller: 'submit_form_download' resources :decline, only: %i[create], controller: 'submit_form_decline' + resources :delegate, only: %i[create], controller: 'submit_form_delegate' resources :invite, only: %i[create], controller: 'submit_form_invite' get :completed + get :delegated end resources :submit_form_draw_signature, only: %i[show], path: 'p', param: 'slug' diff --git a/db/migrate/20260327100000_create_submitter_versions.rb b/db/migrate/20260327100000_create_submitter_versions.rb new file mode 100644 index 00000000..40ae11f9 --- /dev/null +++ b/db/migrate/20260327100000_create_submitter_versions.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class CreateSubmitterVersions < ActiveRecord::Migration[8.1] + def change + create_table :submitter_versions do |t| + t.references :submitter, null: false, foreign_key: true + t.string :slug, null: false + t.string :email + t.string :name + t.string :phone + + t.timestamps + end + + add_index :submitter_versions, :slug + end +end diff --git a/db/schema.rb b/db/schema.rb index c2c39bc1..2615a0ff 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_02_26_193537) do +ActiveRecord::Schema[8.1].define(version: 2026_03_27_100000) do # These are extensions that must be enabled in order to support this database enable_extension "btree_gin" enable_extension "plpgsql" @@ -364,6 +364,18 @@ ActiveRecord::Schema[8.1].define(version: 2026_02_26_193537) do t.index ["template_id"], name: "index_submissions_on_template_id" end + create_table "submitter_versions", force: :cascade do |t| + t.datetime "created_at", null: false + t.string "email" + t.string "name" + t.string "phone" + t.string "slug", null: false + t.bigint "submitter_id", null: false + t.datetime "updated_at", null: false + t.index ["slug"], name: "index_submitter_versions_on_slug" + t.index ["submitter_id"], name: "index_submitter_versions_on_submitter_id" + end + create_table "submitters", force: :cascade do |t| t.bigint "submission_id", null: false t.string "uuid", null: false @@ -541,12 +553,14 @@ ActiveRecord::Schema[8.1].define(version: 2026_02_26_193537) do add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" add_foreign_key "document_generation_events", "submitters" + add_foreign_key "dynamic_document_versions", "dynamic_documents" add_foreign_key "dynamic_documents", "templates" add_foreign_key "email_events", "accounts" add_foreign_key "email_messages", "accounts" add_foreign_key "email_messages", "users", column: "author_id" add_foreign_key "encrypted_configs", "accounts" add_foreign_key "encrypted_user_configs", "users" + add_foreign_key "mcp_tokens", "users" add_foreign_key "oauth_access_grants", "oauth_applications", column: "application_id" add_foreign_key "oauth_access_grants", "users", column: "resource_owner_id" add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id" @@ -556,6 +570,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_02_26_193537) do add_foreign_key "submission_events", "submitters" add_foreign_key "submissions", "templates" add_foreign_key "submissions", "users", column: "created_by_user_id" + add_foreign_key "submitter_versions", "submitters" add_foreign_key "submitters", "submissions" add_foreign_key "template_accesses", "templates" add_foreign_key "template_folders", "accounts" diff --git a/lib/accounts.rb b/lib/accounts.rb index fbbf688c..41b7d761 100644 --- a/lib/accounts.rb +++ b/lib/accounts.rb @@ -175,6 +175,7 @@ module Accounts def can_send_emails?(_account, **_params) return true if Docuseal.multitenant? + return true if Rails.env.development? return true if ENV['SMTP_ADDRESS'].present? EncryptedConfig.exists?(key: EncryptedConfig::EMAIL_SMTP_KEY) diff --git a/lib/submissions/generate_audit_trail.rb b/lib/submissions/generate_audit_trail.rb index ed80a86d..0c7660db 100644 --- a/lib/submissions/generate_audit_trail.rb +++ b/lib/submissions/generate_audit_trail.rb @@ -246,33 +246,25 @@ module Submissions next if submitter.blank? - completed_event = - submission.submission_events.find { |e| e.submitter_id == submitter.id && e.complete_form? } || - SubmissionEvent.new + submission_events = submission.submission_events.select { |e| e.submitter_id == submitter.id } - click_email_event = - submission.submission_events.find { |e| e.submitter_id == submitter.id && e.click_email? } + delegated_event = submission_events.select(&:delegate_form?).max_by(&:event_timestamp) - verify_email_event = - submission.submission_events.find { |e| e.submitter_id == submitter.id && e.email_verified? } + if delegated_event + submission_events = submission_events.select { |e| e.event_timestamp > delegated_event.event_timestamp } + end - is_phone_verified = - submission.template_fields.any? do |e| - e['type'] == 'phone' && e['submitter_uuid'] == submitter.uuid && submitter.values[e['uuid']].present? - end + completed_event = submission_events.find(&:complete_form?) || SubmissionEvent.new - verify_phone_event = - submission.submission_events.find { |e| e.submitter_id == submitter.id && e.phone_verified? } + click_email_event = submission_events.find(&:click_email?) - is_id_verified = - submission.template_fields.any? do |e| - e['type'] == 'verification' && e['submitter_uuid'] == submitter.uuid && submitter.values[e['uuid']].present? - end + verify_email_event = submission_events.find(&:email_verified?) - is_kba_passed = - submission.template_fields.any? do |e| - e['type'] == 'kba' && e['submitter_uuid'] == submitter.uuid && submitter.values[e['uuid']].present? - end + verify_phone_event = submission_events.find(&:phone_verified?) + + is_id_verified = submission_events.any?(&:complete_verification?) + + is_kba_passed = submission_events.any?(&:complete_kba?) info_rows = [ [ @@ -291,7 +283,7 @@ module Submissions submitter.email && (click_email_event || verify_email_event) && { text: "#{I18n.t('email_verification')}: #{I18n.t('verified')}\n" }, - submitter.phone && (is_phone_verified || verify_phone_event) && { + submitter.phone && verify_phone_event && { text: "#{I18n.t('phone_verification')}: #{I18n.t('verified')}\n" }, is_id_verified && { @@ -446,15 +438,23 @@ module Submissions composer.text(I18n.t('event_log'), font_size: 12, padding: [10, 0, 20, 0]) + submitter_versions_index = submission.submitters.each_with_object({}) do |s, h| + h[s.id] = s.submitter_versions.to_a.sort_by(&:created_at) + end + events_data = submission.submission_events.sort_by(&:event_timestamp).filter_map do |event| next if event.event_type.in?(%w[bounce_email complaint_email]) submitter = submission.submitters.find { |e| e.id == event.submitter_id } + versions = submitter_versions_index[submitter.id] || [] + active_version = versions.find { |v| v.created_at > event.event_timestamp } + submitter_name = if event.event_type.include?('sms') || event.event_type.include?('phone') - event.data['phone'] || submitter.phone + event.data['phone'] || active_version&.phone || submitter.phone else - submitter.name || submitter.email || submitter.phone + active_version&.name || active_version&.email || active_version&.phone || + submitter.name || submitter.email || submitter.phone end text = @@ -473,6 +473,10 @@ module Submissions I18n.t("submission_event_names.#{event.event_type}_to_html", submitter_name:), "#{I18n.t(:from)} #{submission.created_by_user.full_name} #{submission.created_by_user.email}" ].join("\n") + elsif event.event_type == 'delegate_form' + from = event.data['old_email'].presence || + versions.reverse.find { |v| v.created_at <= event.event_timestamp }&.then { |v| v.name || v.phone } + I18n.t('submission_event_names.delegate_form_by_html', from:, to: event.data['email']) elsif event.event_type.include?('send_') I18n.t("submission_event_names.#{event.event_type}_to_html", submitter_name:) else diff --git a/lib/submitters/form_configs.rb b/lib/submitters/form_configs.rb index 4db19b32..3ebf9118 100644 --- a/lib/submitters/form_configs.rb +++ b/lib/submitters/form_configs.rb @@ -8,6 +8,7 @@ module Submitters AccountConfig::FORM_PREFILL_SIGNATURE_KEY, AccountConfig::WITH_SIGNATURE_ID, AccountConfig::ALLOW_TO_DECLINE_KEY, + AccountConfig::ALLOW_TO_DELEGATE_KEY, AccountConfig::ENFORCE_SIGNING_ORDER_KEY, AccountConfig::REQUIRE_SIGNING_REASON_KEY, AccountConfig::REUSE_SIGNATURE_KEY, @@ -31,6 +32,7 @@ module Submitters prefill_signature = find_safe_value(configs, AccountConfig::FORM_PREFILL_SIGNATURE_KEY) != false reuse_signature = find_safe_value(configs, AccountConfig::REUSE_SIGNATURE_KEY) != false with_decline = find_safe_value(configs, AccountConfig::ALLOW_TO_DECLINE_KEY) != false + with_delegate = find_safe_value(configs, AccountConfig::ALLOW_TO_DELEGATE_KEY) == true with_partial_download = find_safe_value(configs, AccountConfig::ALLOW_TO_PARTIAL_DOWNLOAD_KEY) != false with_signature_id = find_safe_value(configs, AccountConfig::WITH_SIGNATURE_ID) == true require_signing_reason = find_safe_value(configs, AccountConfig::REQUIRE_SIGNING_REASON_KEY) == true @@ -42,7 +44,7 @@ module Submitters policy_links = find_safe_value(configs, AccountConfig::POLICY_LINKS_KEY) attrs = { completed_button:, with_typed_signature:, with_confetti:, - reuse_signature:, with_decline:, with_partial_download:, + reuse_signature:, with_decline:, with_delegate:, with_partial_download:, policy_links:, enforce_signing_order:, completed_message:, require_signing_reason:, prefill_signature:, with_submitter_timezone:, with_signature_id_reason:, with_signature_id:, with_field_labels:, with_timestamp_seconds: } diff --git a/spec/system/signing_form_spec.rb b/spec/system/signing_form_spec.rb index 7af76c2f..c5ebb4dc 100644 --- a/spec/system/signing_form_spec.rb +++ b/spec/system/signing_form_spec.rb @@ -1159,6 +1159,50 @@ RSpec.describe 'Signing Form' do end end + context 'when decline is enabled' do + let(:template) { create(:template, account:, author:, only_field_types: %w[text]) } + let(:submission) { create(:submission, template:) } + let(:submitter) { create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:) } + + it 'declines the form and shows the declined page' do + visit submit_form_path(slug: submitter.slug) + + find('#decline_button').click + fill_in 'reason', with: 'I do not agree with the terms' + click_button 'Decline' + + expect(page).to have_content('Form has been declined') + + submitter.reload + + expect(submitter.declined_at).to be_present + end + end + + context 'when delegate is enabled' do + let(:template) { create(:template, account:, author:, only_field_types: %w[text]) } + let(:submission) { create(:submission, template:) } + let(:submitter) { create(:submitter, submission:, uuid: template.submitters.first['uuid'], account:) } + + before do + create(:account_config, account:, key: AccountConfig::ALLOW_TO_DELEGATE_KEY, value: true) + end + + it 'delegates the form to another email and shows the delegated page' do + visit submit_form_path(slug: submitter.slug) + + find('#delegate_button').click + fill_in 'email', with: 'delegate@example.com' + click_button 'Delegate' + + expect(page).to have_content('Document has been delegated') + + submitter.reload + + expect(submitter.email).to eq('delegate@example.com') + end + end + context 'when the 2FA email verification is enabled', sidekiq: :inline do let(:template) { create(:template, account:, author:, only_field_types: %w[text]) } let(:submission) { create(:submission, template:) } From 486cc493e6b8d56bb1b2b20b25716bbd331feb66 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 27 Mar 2026 13:35:43 +0200 Subject: [PATCH 06/16] add i18n --- config/locales/i18n.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index 22ec12d5..a75442fd 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -1988,6 +1988,7 @@ es: &es invite_party_by_html: 'Invitado %{invited_submitter_name} por %{submitter_name}' complete_form_by_html: 'Envío completado por %{submitter_name}' api_complete_form_by_html: 'Envío completado vía API por %{submitter_name}' + decline_form_by_html: 'Formulario rechazado por %{submitter_name}' delegate_form_by_html: 'Delegado de %{from} a %{to}' start_verification_by_html: 'Verificación de identidad iniciada por %{submitter_name}' complete_verification_by_html: 'Verificación de identidad completada por %{submitter_name} con %{provider}' @@ -3022,6 +3023,7 @@ it: &it invite_party_by_html: 'Invitato %{invited_submitter_name} da %{submitter_name}' complete_form_by_html: 'Invio completato da %{submitter_name}' api_complete_form_by_html: 'Invio completato tramite API da %{submitter_name}' + decline_form_by_html: 'Modulo rifiutato da %{submitter_name}' delegate_form_by_html: 'Delegato da %{from} a %{to}' start_verification_by_html: "Verifica dell'identità iniziata da %{submitter_name}" complete_verification_by_html: "Verifica dell'identità completata da %{submitter_name} con %{provider}" @@ -4054,6 +4056,7 @@ fr: &fr start_verification_by_html: "Vérification d’identité démarrée par %{submitter_name}" complete_verification_by_html: "Vérification d’identité terminée par %{submitter_name} avec %{provider}" api_complete_form_by_html: "Soumission terminée via API par %{submitter_name}" + decline_form_by_html: "Formulaire refusé par %{submitter_name}" delegate_form_by_html: "Délégué de %{from} à %{to}" start_kba_by_html: "KBA démarré par %{submitter_name}" complete_kba_by_html: "KBA terminé par %{submitter_name}" @@ -5087,6 +5090,7 @@ pt: &pt start_verification_by_html: 'Verificação de identidade iniciada por %{submitter_name}' complete_verification_by_html: 'Verificação de identidade concluída por %{submitter_name} com %{provider}' api_complete_form_by_html: 'Submissão concluída via API por %{submitter_name}' + decline_form_by_html: 'Formulário recusado por %{submitter_name}' delegate_form_by_html: 'Delegado de %{from} para %{to}' start_kba_by_html: 'KBA iniciada por %{submitter_name}' complete_kba_by_html: 'KBA concluída por %{submitter_name}' @@ -6120,6 +6124,7 @@ de: &de start_verification_by_html: 'Identitätsüberprüfung gestartet von %{submitter_name}' complete_verification_by_html: 'Identitätsüberprüfung abgeschlossen von %{submitter_name} mit %{provider}' api_complete_form_by_html: 'Einreichung über API abgeschlossen von %{submitter_name}' + decline_form_by_html: 'Formular abgelehnt von %{submitter_name}' delegate_form_by_html: 'Delegiert von %{from} an %{to}' start_kba_by_html: 'KBA gestartet von %{submitter_name}' complete_kba_by_html: 'KBA abgeschlossen von %{submitter_name}' @@ -7550,6 +7555,7 @@ nl: &nl start_verification_by_html: "Identiteitsverificatie gestart door %{submitter_name}" complete_verification_by_html: "Identiteitsverificatie voltooid door %{submitter_name} met %{provider}" api_complete_form_by_html: "Inzending via API voltooid door %{submitter_name}" + decline_form_by_html: "Formulier geweigerd door %{submitter_name}" delegate_form_by_html: "Gedelegeerd van %{from} naar %{to}" start_kba_by_html: "KBA gestart door %{submitter_name}" complete_kba_by_html: "KBA voltooid door %{submitter_name}" From 407816b5762aa51c4cb1e2fb8247d0d5bd656221 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 27 Mar 2026 14:51:14 +0200 Subject: [PATCH 07/16] redirect delegate completed --- app/controllers/submit_form_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/submit_form_controller.rb b/app/controllers/submit_form_controller.rb index 96f95f08..4155b9f6 100644 --- a/app/controllers/submit_form_controller.rb +++ b/app/controllers/submit_form_controller.rb @@ -8,7 +8,7 @@ class SubmitFormController < ApplicationController skip_authorization_check before_action :load_submitter, only: %i[show update completed] - before_action :maybe_redirect_delegated, only: :show + before_action :maybe_redirect_delegated, only: %i[show completed] before_action :maybe_render_locked_page, only: :show before_action :maybe_require_link_2fa, only: %i[show] From db6e2eb7ca7f5cf22fd7df647742e1f1a7f6e4d4 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Fri, 27 Mar 2026 17:03:12 +0200 Subject: [PATCH 08/16] preserve order with verification --- app/views/submissions/_submitters_order.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/submissions/_submitters_order.html.erb b/app/views/submissions/_submitters_order.html.erb index c61820cc..f0398008 100644 --- a/app/views/submissions/_submitters_order.html.erb +++ b/app/views/submissions/_submitters_order.html.erb @@ -1,4 +1,4 @@ -<% if template.preferences['submitters_order'] == 'preserved' || template.submitters.any? { |s| s['order'] } %> +<% if template.preferences['submitters_order'] == 'preserved' || template.submitters.any? { |s| s['order'] } || (template.submitters.size > 1 && template.fields.any? { |f| f['type'] == 'verification' }) %> <%= f.hidden_field :preserve_order, value: '1' %> <% elsif template.submitters.size > 1 %>
    From 640b45172723323458da9615737a35701bd92273 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 28 Mar 2026 09:07:52 +0200 Subject: [PATCH 09/16] adjust shared link --- app/views/templates/_title.html.erb | 2 +- app/views/templates_share_link/show.html.erb | 10 ++++++++-- config/locales/i18n.yml | 7 +++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/views/templates/_title.html.erb b/app/views/templates/_title.html.erb index 9c3abc68..7debbb4d 100644 --- a/app/views/templates/_title.html.erb +++ b/app/views/templates/_title.html.erb @@ -49,7 +49,7 @@ <% end %>
    <% end %> - <%= link_to template_share_link_path(template), class: "absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2 #{'btn-disabled text-base-100' if template.variables_schema.present?}", data: { turbo_frame: :modal } do %> + <%= link_to template_share_link_path(template), class: "absolute md:relative bottom-0 right-0 btn btn-xs md:btn-sm whitespace-nowrap btn-neutral text-white mt-1 px-2", data: { turbo_frame: :modal } do %> <%= svg_icon('link', class: 'w-4 h-4 md:w-6 md:h-6') %> <%= t('link') %> diff --git a/app/views/templates_share_link/show.html.erb b/app/views/templates_share_link/show.html.erb index 43149a29..b654c8d7 100644 --- a/app/views/templates_share_link/show.html.erb +++ b/app/views/templates_share_link/show.html.erb @@ -2,6 +2,12 @@ <% enough_defined_submitters = Templates.filter_undefined_submitters(@template.submitters).size < 2 %> <%= render 'shared/turbo_modal_large', title: t('share_link') do %>
    + <% if @template.variables_schema.present? %> +
    + <%= svg_icon('info_circle', class: 'stroke-current shrink-0 h-6 w-6') %> +
    <%= t('dynamic_template_with_variables_cannot_be_used_via_shared_link') %>
    +
    + <% end %> <%= form_for @template, url: template_share_link_path(@template), method: :post, html: { id: 'shared_link_form', autocomplete: 'off', class: 'mt-3' }, data: { close_on_submit: false } do |f| %> <% if @template.preferences&.dig('require_email_2fa') || @template.preferences&.dig('require_phone_2fa') %>
    <% end %> - <% if multiple_submitters && !enough_defined_submitters %> + <% if multiple_submitters && !enough_defined_submitters && @template.variables_schema.blank? %>
    <%= svg_icon('info_circle', class: 'stroke-current shrink-0 h-6 w-6 mt-1') %>
    <%= t('this_template_has_multiple_parties_which_prevents_the_use_of_a_sharing_link') %>
    diff --git a/config/locales/i18n.yml b/config/locales/i18n.yml index a75442fd..0baaa19b 100644 --- a/config/locales/i18n.yml +++ b/config/locales/i18n.yml @@ -791,6 +791,7 @@ en: &en share_link: Share link enable_shared_link: Enable shared link share_link_is_currently_disabled: Share link is currently disabled + dynamic_template_with_variables_cannot_be_used_via_shared_link: Dynamic template with variables can't be used via a shared link select_data_residency: Select data residency account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: '%{account_name} has invited you to fill and sign documents online effortlessly with a secure, fast, and user-friendly digital document signing solution.' review_or_download_completed_documents_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: Review or download completed documents. Fill and sign documents online effortlessly with a secure, fast, and user-friendly digital document signing solution. @@ -1825,6 +1826,7 @@ es: &es share_link: Enlace para compartir enable_shared_link: Habilitar enlace compartido share_link_is_currently_disabled: El enlace compartido está deshabilitado actualmente + dynamic_template_with_variables_cannot_be_used_via_shared_link: La plantilla dinámica con variables no se puede usar mediante un enlace compartido select_data_residency: Seleccionar ubicación de datos account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: '%{account_name} te ha invitado a completar y firmar documentos en línea fácilmente con una solución de firma digital segura, rápida y fácil de usar.' review_or_download_completed_documents_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: Revisa o descarga los documentos completados. Completa y firma documentos en línea fácilmente con una solución de firma digital segura, rápida y fácil de usar. @@ -2859,6 +2861,7 @@ it: &it share_link: Link di condivisione enable_shared_link: Abilita link condiviso share_link_is_currently_disabled: Il link condiviso è attualmente disabilitato + dynamic_template_with_variables_cannot_be_used_via_shared_link: Il template dinamico con variabili non può essere utilizzato tramite link condiviso select_data_residency: Seleziona la residenza dei dati account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: '%{account_name} ti ha invitato a compilare e firmare documenti online con facilità utilizzando una soluzione di firma digitale sicura, veloce e facile da usare.' review_or_download_completed_documents_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: Rivedi o scarica i documenti completati. Compila e firma documenti online facilmente con una soluzione di firma digitale sicura, veloce e facile da usare. @@ -3891,6 +3894,7 @@ fr: &fr share_link: Lien de partage enable_shared_link: Activer le lien partagé share_link_is_currently_disabled: Le lien de partage est actuellement désactivé + dynamic_template_with_variables_cannot_be_used_via_shared_link: Le modèle dynamique avec des variables ne peut pas être utilisé via un lien partagé select_data_residency: Sélectionner la résidence des données account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: "%{account_name} vous a invité à remplir et signer des documents en ligne facilement, grâce à une solution de signature numérique sécurisée, rapide et conviviale." review_or_download_completed_documents_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: Consultez ou téléchargez les documents complétés. Remplissez et signez des documents en ligne facilement, grâce à une solution de signature numérique sécurisée, rapide et conviviale. @@ -4925,6 +4929,7 @@ pt: &pt share_link: Link de compartilhamento enable_shared_link: Ativar link compartilhado share_link_is_currently_disabled: O link compartilhado está desativado no momento + dynamic_template_with_variables_cannot_be_used_via_shared_link: O template dinâmico com variáveis não pode ser usado por meio de link compartilhado select_data_residency: Selecionar local dos dados account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: '%{account_name} convidou você para preencher e assinar documentos online com facilidade usando uma solução de assinatura digital segura, rápida e fácil de usar.' review_or_download_completed_documents_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: Reveja ou baixe os documentos concluídos. Preencha e assine documentos online com facilidade usando uma solução de assinatura digital segura, rápida e fácil de usar. @@ -5959,6 +5964,7 @@ de: &de share_link: Freigabelink enable_shared_link: Freigabelink aktivieren share_link_is_currently_disabled: Freigabelink ist derzeit deaktiviert + dynamic_template_with_variables_cannot_be_used_via_shared_link: Dynamische Vorlage mit Variablen kann nicht über einen Freigabelink verwendet werden select_data_residency: Datenstandort auswählen account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: '%{account_name} hat Sie eingeladen, Dokumente mühelos online mit einer sicheren, schnellen und benutzerfreundlichen digitalen Signaturlösung auszufüllen und zu unterschreiben.' review_or_download_completed_documents_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: Überprüfen oder laden Sie abgeschlossene Dokumente herunter. Füllen und unterschreiben Sie Dokumente mühelos online mit einer sicheren, schnellen und benutzerfreundlichen digitalen Signaturlösung. @@ -7391,6 +7397,7 @@ nl: &nl share_link: Deellink enable_shared_link: Deellink inschakelen share_link_is_currently_disabled: Deellink is momenteel uitgeschakeld + dynamic_template_with_variables_cannot_be_used_via_shared_link: Dynamisch sjabloon met variabelen kan niet worden gebruikt via een deellink select_data_residency: Gegevensresidentie selecteren account_name_has_invited_you_to_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: "%{account_name} heeft u uitgenodigd om documenten online moeiteloos in te vullen en te ondertekenen met een veilige, snelle en gebruiksvriendelijke digitale ondertekenoplossing." review_or_download_completed_documents_fill_and_sign_documents_online_effortlessly_with_a_secure_fast_and_user_friendly_digital_document_signing_solution: Bekijk of download voltooide documenten. Vul en onderteken documenten online moeiteloos met een veilige, snelle en gebruiksvriendelijke digitale ondertekenoplossing. From 6ffc88b4ed0b6f8e57a17876f1434d100b17c768 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 28 Mar 2026 09:27:50 +0200 Subject: [PATCH 10/16] fix n+1 --- app/views/submission_events/index.html.erb | 2 +- lib/submissions/generate_audit_trail.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/submission_events/index.html.erb b/app/views/submission_events/index.html.erb index 5c454288..1ce6c8c0 100644 --- a/app/views/submission_events/index.html.erb +++ b/app/views/submission_events/index.html.erb @@ -20,7 +20,7 @@ <% end %>

  • - <% submitter_versions_index = @submission.submitters.each_with_object({}) { |s, h| h[s.id] = s.submitter_versions.to_a.sort_by(&:created_at) } %> + <% submitter_versions_index = @submission.submitters.preload(:submitter_versions).each_with_object({}) { |s, h| h[s.id] = s.submitter_versions.to_a.sort_by(&:created_at) } %> <% @submission.submission_events.order(:event_timestamp).each do |event| %> <% submitter = @submission.submitters.find { |e| e.id == event.submitter_id } %> <% bg_class = event_colors[submitters_uuids.index(submitter.uuid) % event_colors.length] %> diff --git a/lib/submissions/generate_audit_trail.rb b/lib/submissions/generate_audit_trail.rb index 0c7660db..794d0dc7 100644 --- a/lib/submissions/generate_audit_trail.rb +++ b/lib/submissions/generate_audit_trail.rb @@ -438,7 +438,7 @@ module Submissions composer.text(I18n.t('event_log'), font_size: 12, padding: [10, 0, 20, 0]) - submitter_versions_index = submission.submitters.each_with_object({}) do |s, h| + submitter_versions_index = submission.submitters.preload(:submitter_versions).each_with_object({}) do |s, h| h[s.id] = s.submitter_versions.to_a.sort_by(&:created_at) end From 8d11f5ddaea978b09c1e6b40344fee05e9e81230 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 28 Mar 2026 12:51:00 +0200 Subject: [PATCH 11/16] adjust email 2fa --- lib/email_verification_codes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/email_verification_codes.rb b/lib/email_verification_codes.rb index a65da175..8fdfaacc 100644 --- a/lib/email_verification_codes.rb +++ b/lib/email_verification_codes.rb @@ -20,7 +20,7 @@ module EmailVerificationCodes def build_totp_secret(value) ROTP::Base32.encode( Digest::SHA1.digest( - [Rails.application.secret_key_base, value].join(':') + [Rails.application.secret_key_base, 'form_email_2fa', value].join(':') ) ) end From a2598b30f7efa55bb878e61308bdcc55d1cdf12e Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 28 Mar 2026 13:48:39 +0200 Subject: [PATCH 12/16] optimize build --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6a7db422..5f9a3dc4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,7 +52,7 @@ ENV OPENSSL_CONF=/etc/openssl_legacy.cnf WORKDIR /app -RUN apk add --no-cache sqlite-dev libpq-dev vips-dev yaml-dev redis libheif vips-heif gcompat ttf-freefont onnxruntime && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf +RUN apk add --no-cache libpq yaml vips redis vips-heif gcompat ttf-freefont onnxruntime && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal @@ -70,7 +70,7 @@ activate = 1' >> /etc/openssl_legacy.cnf COPY --chown=docuseal:docuseal ./Gemfile ./Gemfile.lock ./ -RUN apk add --no-cache build-base git && bundle install && apk del --no-cache build-base git && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf && ln -sf /usr/lib/libonnxruntime.so.1 $(ruby -e "print Dir[Gem::Specification.find_by_name('onnxruntime').gem_dir + '/vendor/*.so'].first") +RUN apk add --no-cache build-base git libpq-dev yaml-dev && bundle install && apk del --no-cache build-base git libpq-dev yaml-dev && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf && ln -sf /usr/lib/libonnxruntime.so.1 $(ruby -e "print Dir[Gem::Specification.find_by_name('onnxruntime').gem_dir + '/vendor/*.so'].first") COPY --chown=docuseal:docuseal ./bin ./bin COPY --chown=docuseal:docuseal ./app ./app From 8d3f0b5b98a7b1e7cfa4bf534a1cb89188eea28a Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 28 Mar 2026 14:35:09 +0200 Subject: [PATCH 13/16] silence not found trace --- app/controllers/errors_controller.rb | 4 ++-- config/initializers/silence_errors.rb | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 config/initializers/silence_errors.rb diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb index 0c9e3632..f6401243 100644 --- a/app/controllers/errors_controller.rb +++ b/app/controllers/errors_controller.rb @@ -40,7 +40,7 @@ class ErrorsController < ActionController::Base render json: { status: error_status_code, error: }.compact, status: error_status_code end - f.html { render error_status_code.to_s, status: error_status_code } + f.any { render error_status_code.to_s, status: error_status_code } end end @@ -51,7 +51,7 @@ class ErrorsController < ActionController::Base headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, PATCH, DELETE, OPTIONS' headers['Access-Control-Allow-Headers'] = '*' headers['Access-Control-Max-Age'] = '1728000' - headers['Access-Control-Allow-Credentials'] = true + headers['Access-Control-Allow-Credentials'] = 'true' end def error_status_code diff --git a/config/initializers/silence_errors.rb b/config/initializers/silence_errors.rb new file mode 100644 index 00000000..ab3090b5 --- /dev/null +++ b/config/initializers/silence_errors.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SilenceErrors + def log_error(request, wrapper) + return if wrapper.status_code == 404 + + super + end +end + +ActionDispatch::DebugExceptions.prepend(SilenceErrors) From cba3e74f3a1d25d97bc7130ed16692873ba20d0a Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 28 Mar 2026 16:29:34 +0200 Subject: [PATCH 14/16] optimize build --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5f9a3dc4..0ba69973 100644 --- a/Dockerfile +++ b/Dockerfile @@ -70,7 +70,7 @@ activate = 1' >> /etc/openssl_legacy.cnf COPY --chown=docuseal:docuseal ./Gemfile ./Gemfile.lock ./ -RUN apk add --no-cache build-base git libpq-dev yaml-dev && bundle install && apk del --no-cache build-base git libpq-dev yaml-dev && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales}']" | xargs rm -rf && ln -sf /usr/lib/libonnxruntime.so.1 $(ruby -e "print Dir[Gem::Specification.find_by_name('onnxruntime').gem_dir + '/vendor/*.so'].first") +RUN apk add --no-cache build-base git libpq-dev yaml-dev && bundle install && apk del --no-cache build-base git libpq-dev yaml-dev && rm -rf ~/.bundle /usr/local/bundle/cache && ruby -e "puts Dir['/usr/local/bundle/**/{spec,rdoc,resources/shared,resources/collation,resources/locales,resources/unicode_data/properties}'] + Dir['/usr/local/bundle/gems/*/{test,tests,examples,sample,misc,doc,docs}'] + Dir['/usr/local/bundle/gems/*/ext/**/*.{c,h,o,S}']" | xargs rm -rf && ln -sf /usr/lib/libonnxruntime.so.1 $(ruby -e "print Dir[Gem::Specification.find_by_name('onnxruntime').gem_dir + '/vendor/*.so'].first") COPY --chown=docuseal:docuseal ./bin ./bin COPY --chown=docuseal:docuseal ./app ./app From ffb75238b4981644fba985673d340c43508bb46f Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sat, 28 Mar 2026 16:33:21 +0200 Subject: [PATCH 15/16] disable confetti by default --- app/javascript/form.js | 2 +- .../personalization_settings/_form_toggle_options.html.erb | 4 ++-- lib/submitters/form_configs.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/javascript/form.js b/app/javascript/form.js index 1fbd54c8..00ec8743 100644 --- a/app/javascript/form.js +++ b/app/javascript/form.js @@ -36,7 +36,7 @@ safeRegisterElement('submission-form', class extends HTMLElement { expand: ['true', 'false'].includes(this.dataset.expand) ? this.dataset.expand === 'true' : null, withSignatureId: this.dataset.withSignatureId === 'true', requireSigningReason: this.dataset.requireSigningReason === 'true', - withConfetti: this.dataset.withConfetti !== 'false', + withConfetti: this.dataset.withConfetti === 'true', withFieldLabels: this.dataset.withFieldLabels !== 'false', withDisclosure: this.dataset.withDisclosure === 'true', reuseSignature: this.dataset.reuseSignature !== 'false', diff --git a/app/views/personalization_settings/_form_toggle_options.html.erb b/app/views/personalization_settings/_form_toggle_options.html.erb index f57dbee8..195ef0f1 100644 --- a/app/views/personalization_settings/_form_toggle_options.html.erb +++ b/app/views/personalization_settings/_form_toggle_options.html.erb @@ -1,4 +1,4 @@ -<% account_config = AccountConfig.where(account: current_account, key: AccountConfig::FORM_WITH_CONFETTI_KEY).first_or_initialize(value: true) %> +<% account_config = AccountConfig.where(account: current_account, key: AccountConfig::FORM_WITH_CONFETTI_KEY).first_or_initialize(value: false) %> <% if can?(:manage, account_config) %>
    <%= form_for account_config, url: account_configs_path, method: :post do |f| %> @@ -8,7 +8,7 @@ <%= t('show_confetti_on_successful_completion') %> - <%= f.check_box :value, { class: 'toggle', checked: account_config.value != false }, '1', '0' %> + <%= f.check_box :value, { class: 'toggle', checked: account_config.value == true }, '1', '0' %>
    <% end %> diff --git a/lib/submitters/form_configs.rb b/lib/submitters/form_configs.rb index 3ebf9118..1b21fa70 100644 --- a/lib/submitters/form_configs.rb +++ b/lib/submitters/form_configs.rb @@ -28,7 +28,7 @@ module Submitters completed_button = find_safe_value(configs, AccountConfig::FORM_COMPLETED_BUTTON_KEY) || {} completed_message = find_safe_value(configs, AccountConfig::FORM_COMPLETED_MESSAGE_KEY) || {} with_typed_signature = find_safe_value(configs, AccountConfig::ALLOW_TYPED_SIGNATURE) != false - with_confetti = find_safe_value(configs, AccountConfig::FORM_WITH_CONFETTI_KEY) != false + with_confetti = find_safe_value(configs, AccountConfig::FORM_WITH_CONFETTI_KEY) == true prefill_signature = find_safe_value(configs, AccountConfig::FORM_PREFILL_SIGNATURE_KEY) != false reuse_signature = find_safe_value(configs, AccountConfig::REUSE_SIGNATURE_KEY) != false with_decline = find_safe_value(configs, AccountConfig::ALLOW_TO_DECLINE_KEY) != false From a98caad968e986869d50f1810d7afcdab2886764 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Sun, 29 Mar 2026 10:24:27 +0300 Subject: [PATCH 16/16] use musl build --- Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0ba69973..969084aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ RUN apk --no-cache add fontforge wget && \ wget https://github.com/Maxattax97/gnu-freefont/raw/master/ttf/FreeSans.ttf && \ wget https://github.com/impallari/DancingScript/raw/master/OFL.txt && \ wget -O /model.onnx "https://github.com/docusealco/fields-detection/releases/download/2.0.0/model_704_int8.onnx" && \ - wget -O pdfium-linux.tgz "https://github.com/docusealco/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz" && \ + wget -O pdfium-linux.tgz "https://github.com/bblanchon/pdfium-binaries/releases/latest/download/pdfium-linux-musl-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz" && \ mkdir -p /pdfium-linux && \ tar -xzf pdfium-linux.tgz -C /pdfium-linux @@ -47,12 +47,11 @@ FROM ruby:4.0.1-alpine AS app ENV RAILS_ENV=production ENV BUNDLE_WITHOUT="development:test" -ENV LD_PRELOAD=/lib/libgcompat.so.0 ENV OPENSSL_CONF=/etc/openssl_legacy.cnf WORKDIR /app -RUN apk add --no-cache libpq yaml vips redis vips-heif gcompat ttf-freefont onnxruntime && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf +RUN apk add --no-cache libpq vips redis vips-heif ttf-freefont onnxruntime && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal